· 9 years ago · Oct 27, 2016, 09:34 PM
1-- Author: Encreedem
2-- Link: http://www.computercraft.info/forums2/index.php?/topic/13944-161-graffiti-the-first-ide-for-guis/
3
4-- Notes to myself: Fix this until the 1.7 update!
5-- Well... at least I tried.
6-- TODO: Bug: ScrollView doesn't get updated immediately when its content changes.
7-- TODO: Clean up all the code below this comment.
8
9local version = "1.7.2"
10
11-- Used to save temporary data of objects.
12ObjectData = {
13 Input = {};
14 List = {};
15 FileSelector = {};
16 Button = {};
17 CheckBox = {};
18 RadioButton = {};
19 Slider = {};
20 DropDownList = {};
21}
22
23UserData = {}
24
25--Monitor
26local monitor = nil
27
28-- Colors
29local ObjectColors = {}
30
31-- Color Themes
32local ColorThemes = {}
33ColorThemes.Default = { -- Used colors: As many as I could get. (black background)
34 background = colors.black;
35 text = colors.white;
36 disabled = colors.lightGray;
37 disabledText = colors.black;
38 Button = { default = colors.red; active = colors.lime; text = colors.white };
39 ProgressBar = { low = colors.red; medium = colors.yellow; high = colors.lime; background = colors.gray; };
40 Input = { default = colors.gray; text = colors.white; active = colors.lightBlue; };
41 List = { default = colors.gray; active = colors.lightBlue; };
42 FileSelector = { default = colors.red; text = colors.white; dir = colors.lime; file = colors.white; active = colors.lime; };
43 CheckBox = { default = colors.white; active = colors.green; };
44 RadioButton = { default = colors.white; active = colors.lime };
45 Slider = { default = colors.orange; text = colors.black; active = colors.white; activeText = colors.black };
46 DropDownList = { default = colors.gray; text = colors.white; active = colors.lightGray; };
47 DefaultButtons = {
48 default = colors.red;
49 text = colors.white;
50 Quit = {
51 default = colors.red;
52 text = colors.white;
53 };
54 };
55 Editor = {
56 new = colors.white;
57 active = colors.lime;
58 move = colors.magenta;
59 scale = colors.pink;
60 marker = colors.gray;
61 textInputMarker = colors.yellow;
62 editMarker = colors.lime;
63 alignmentTrue = colors.lime;
64 alignmentFalse = colors.red;
65 selector1 = colors.gray;
66 selector2 = colors.lightGray;
67 selectorText = colors.white;
68 };
69 Container = {
70 Panel = {
71 border = colors.white;
72 };
73 ScrollView = {
74 border = colors.white;
75 scrollBackground = colors.lightGray;
76 scrollForeground = colors.gray;
77 };
78 };
79}
80
81ColorThemes["Windows CC"] = { -- Used colors: none, just white, lightGray, gray and black... (white background)
82 background = colors.white;
83 text = colors.black;
84 disabled = colors.gray;
85 disabledText = colors.white;
86 Button = { default = colors.lightGray; active = colors.lightBlue; text = colors.black };
87 ProgressBar = { low = colors.lime; medium = colors.lime; high = colors.lime; background = colors.lightGray; };
88 Input = { default = colors.lightGray; text = colors.black; active = colors.white; };
89 List = { default = colors.lightGray; active = colors.lightBlue; };
90 FileSelector = { default = colors.gray; text = colors.black; dir = colors.blue; file = colors.black; active = colors.lightBlue; };
91 CheckBox = { default = colors.gray; active = colors.lightBlue; };
92 RadioButton = { default = colors.gray; active = colors.lightBlue; };
93 Slider = { default = colors.gray; text = colors.black; active = colors.white; activeText = colors.black };
94 DropDownList = { default = colors.gray; text = colors.white; active = colors.lightBlue; };
95 DefaultButtons = {
96 default = colors.lightGray;
97 text = colors.black;
98 Quit = {
99 default = colors.red;
100 text = colors.white;
101 };
102 };
103 Editor = {
104 new = colors.lightBlue;
105 active = colors.black;
106 move = colors.magenta;
107 scale = colors.pink;
108 marker = colors.gray;
109 textInputMarker = colors.black;
110 editMarker = colors.lightBlue;
111 alignmentTrue = colors.lime;
112 alignmentFalse = colors.red;
113 selector1 = colors.gray;
114 selector2 = colors.lightGray;
115 selectorText = colors.white;
116 };
117 Container = {
118 Panel = {
119 border = colors.lightGray;
120 };
121 ScrollView = {
122 border = colors.lightGray;
123 scrollBackground = colors.lightGray;
124 scrollForeground = colors.gray;
125 };
126 };
127}
128
129ColorThemes.Fire = { -- Used colors: red, orange, yellow, lightGray, gray background
130 background = colors.gray;
131 text = colors.black;
132 disabled = colors.black;
133 disabledText = colors.white;
134 Button = { default = colors.orange; active = colors.red; text = colors.black };
135 ProgressBar = { low = colors.yellow; medium = colors.orange; high = colors.red; background = colors.lightGray; };
136 Input = { default = colors.yellow; text = colors.black; active = colors.orange; };
137 List = { default = colors.orange; active = colors.red; };
138 FileSelector = { default = colors.orange; text = colors.black; dir = colors.lime; file = colors.white; active = colors.lime; };
139 CheckBox = { default = colors.white; active = colors.red; };
140 RadioButton = { default = colors.white; active = colors.red };
141 Slider = { default = colors.orange; text = colors.black; active = colors.red; activeText = colors.black };
142 DropDownList = { default = colors.orange; text = colors.black; active = colors.red; };
143 DefaultButtons = {
144 default = colors.orange;
145 text = colors.black;
146 Quit = {
147 default = colors.red;
148 text = colors.white;
149 };
150 };
151 Editor = {
152 new = colors.lightBlue;
153 active = colors.white;
154 move = colors.blue;
155 scale = colors.lightBlue;
156 marker = colors.lightGray;
157 textInputMarker = colors.orange;
158 editMarker = colors.red;
159 alignmentTrue = colors.lime;
160 alignmentFalse = colors.red;
161 selector1 = colors.gray;
162 selector2 = colors.lightGray;
163 selectorText = colors.white;
164 };
165 Container = {
166 Panel = {
167 border = colors.lightGray;
168 };
169 ScrollView = {
170 border = colors.lightGray;
171 scrollBackground = colors.lightGray;
172 scrollForeground = colors.yellow;
173 };
174 };
175}
176
177ObjectColors = ColorThemes.Default
178
179-- Languages
180local Languages = {
181 ["en-US"] = {
182 back = " < ";
183 cancel = "Cancel";
184 colorTheme = "Color Theme";
185 done = "Done";
186 editEntries = "Edit Entries";
187 finish = "Finish";
188 finished = "Done";
189 language = "Language";
190 lastWindow = "Last Window";
191 moveDown = "Move Down";
192 moveUp = "Move Up";
193 newEntry = "New Entry";
194 next = "Next";
195 ok = "OK";
196 options = "Options";
197 quit = " X ";
198 refresh = "Refresh";
199 remove = "Remove";
200
201 setupText1 = "It seems like you've started Graffiti for";
202 setupText2 = "the first time on this computer.";
203 setupText3 = "This setup will create all neccessary data that";
204 setupText4 = "Graffiti needs to run and it allows you to set";
205 setupText5 = "specific settings.";
206
207 skipSetup = "Skip setup (use default settings)";
208 chooseSettings1 = "Please specify these settings:";
209 chooseSettings2 = "(You can change them later if you want)";
210 showDataFolder = "Show Data folder";
211 hideDataFolder = "Hide Data folder";
212 };
213}
214
215local Text = Languages["en-US"]
216
217-- Sizes
218local maxX, maxY = 51, 19
219local Size = {
220 Button = { width = 10; height = 3; };
221 ProgressBar = { length = 10; };
222 Slider = { length = 10; };
223 DropDownList = { width=10 };
224 Container = { width = 20; height = 10; };
225}
226
227-- Files Info
228local root = "/" -- The path where the data-folder will be created by default.
229local dataFolderPath = nil
230local LoadedFiles = {}
231local currentProject = "Default"
232
233-- Converter
234local Converter = {}
235
236-- API
237local initDone = false
238local isAPI = false -- Determines whether the program has been loaded as an API
239local VariableValues = {}
240local ProgressBarValues = {}
241
242-- Editor
243local currentEditorAction = nil
244local editMode = false
245local lastWindow = nil
246local RightClickActions = {"Attributes", "Delete" }
247local saveAfterQuit = true
248local selectedObject = nil
249local selectedObjectDragged = false
250
251-- Custom Windows
252local showCustomWindow = nil
253local ListEditorList = {}
254local CustomWindows = {}
255local CustomFunctions = {}
256
257-- Tables
258local Args = { ... }
259local Sides = rs.getSides()
260local ObjectTypes = { "Button", "Text", "Variable", "ProgressBar", "Input", "List", "CheckBox", "RadioButton", "Slider", "DropDownList", "Panel", "ScrollView" }
261local EventTypes = { "quit", "button_clicked", "button_toggled", "selection_changed", "text_changed", "checked", "radio_changed", "slider_changed" }
262Objects = {}
263Windows = { Children = {} }
264local WindowBuffer = nil
265local DefaultButtons = {}
266local Shortcuts = {}
267
268-- Other
269local quit = false
270local doLog = true -- Determines wheter a log file should be created or not.
271local logFileLoaded = false
272local out = term -- Output: either "term" or the monitor
273local outIsTerm = false
274local changeButtonColor = true
275local currentWindow = "Main"
276
277-- Settings
278local Settings = {
279 root = root;
280 startupProject = currentProject;
281 language = "en-US";
282 colorTheme = "Default";
283 hideDataFolder = false;
284}
285
286-- >> User Data: (This section is just for YOU! :D)
287
288--[[ How to make your own functions:
289Normal "button_clicked" or "button_toggled" function:
290Note: toggleState is only needed if the buttons'
291funcType-attribute is set to "toggle function"
292
293UserData.toggleTest = function(state)
294 rs.setOutput("back", state)
295end
296
297If you don't want to edit this file then you can
298just load it as an API via "os.loadapi(Graffiti)"
299and use "Graffiti.pullEvent()".
300]]
301
302function UserData.test()
303 sleep(1)
304end
305
306function UserData
307
308function UserData.refresh()
309 drawWindow()
310end
311
312function UserData.toggleTest(toggleState)
313 rs.setOutput("front", toggleState)
314end
315
316--[[ "Load" function
317You can specify a string in the "load" attribute
318of some objects.
319The function in the "UserData" folder gets called
320every time the object gets drawn.
321]]
322function UserData.getRandomNumber()
323 return math.random(100)
324end
325
326function UserData.getTime()
327 return textutils.formatTime(os.time(), true)
328end
329
330--[[ WARNING!
331Everything below this comment
332shouldn't be edited!
333If you do so and the program doesn't work anymore
334then it's your fault!
335]]
336
337--[[ Displays the text, the content of a table
338or a star in the upper left corner until you press
339a key.]]
340function dBug(text)
341 out.setCursorPos(1, 1)
342
343 if (text == nil) then
344 out.write("*")
345 getKeyInput()
346 out.setCursorPos(1, 1)
347 out.write(" ")
348 return
349 elseif (type(text) == "table") then
350 for key, value in pairs(text) do
351 print(key .. ": " .. tostring(value))
352 end
353 else
354 out.write(text)
355 end
356
357 getKeyInput()
358end
359
360-- >> Type extensions
361
362function string:startsWith(text)
363 assert(self)
364 assert(text)
365
366 local substring = text:sub(1, #text)
367 return (substring == text)
368end
369
370function string:endsWith(text)
371 assert(self)
372 assert(text)
373
374 local sStart, sEnd = string.find(self, text, (#text * -1))
375 return (sStart ~= nil)
376end
377
378-- Checks whether the table contains the given content-parameter.
379function table:contains(content)
380 if (self and type(self) == "table") then
381 for _, value in pairs(self) do
382 if (value == content) then
383 return true
384 end
385 end
386
387 return false
388 else
389 return false
390 end
391end
392
393-- >> Files
394local Files = {}
395
396-- Returns whether the filename ends with the
397-- given extension.
398function Files.endsWith(filename, extension)
399 assert(filename)
400 assert(extension)
401
402 local sStart, sEnd = string.find(filename, extension, (#extension * -1))
403 return (sStart ~= nil)
404end
405
406-- Serializes a normal or serialized table into a
407-- readable format which can be saved in a file.
408function Files.serialize(toSave)
409 local saveText
410 local serialized = ""
411 local indentation = 0
412 local indent = false
413
414 if (type(toSave) == "string") then
415 saveText = toSave
416 elseif (type(toSave) == "table") then
417 saveText = textutils.serialize(toSave)
418 else
419 error("Can't save variable of type .. " .. type(toSave) .. "!", 1)
420 end
421
422 for char in saveText:gmatch(".") do
423 if (char == "{") then
424 indentation = indentation + 1
425
426 serialized = serialized .. char
427 serialized = serialized .. "\n"
428 indent = true
429 elseif (char == ",") then
430 serialized = serialized .. char .. "\n"
431 indent = true
432 elseif (char == "}") then
433 indentation = indentation - 1
434 serialized = serialized .. string.rep("\t", indentation)
435 serialized = serialized .. char
436 indent = true
437 else
438 if indent then
439 serialized = serialized .. string.rep("\t", indentation)
440 indent = false
441 end
442
443 serialized = serialized .. char
444 end
445 end
446
447 serialized = serialized:gsub(" ", "<SPACE>")
448 return serialized
449end
450
451-- Unserializes a string that has been serialized
452-- using the "Files.serialize" function into a
453-- table.
454function Files.unserialize(str)
455 local saveText = str
456 saveText = saveText:gsub("\n", "")
457 saveText = saveText:gsub("\t", "")
458 saveText = saveText:gsub(" ", "")
459 saveText = saveText:gsub("<SPACE>", " ")
460 saveText = textutils.unserialize(saveText)
461
462 return saveText
463end
464
465-- Initializes the Graffiti data folder.
466function Files.init()
467 if (fs.exists(fs.combine(root, ".GraffitiData"))) then
468 dataFolderPath = fs.combine(root, ".GraffitiData")
469 elseif (fs.exists(fs.combine(root, "GraffitiData"))) then
470 dataFolderPath = fs.combine(root, "GraffitiData")
471 else
472 runSetup()
473 end
474
475 for key, value in pairs(Files) do
476 if (type(value) == "table") then -- If it's an actual file type instead of a function.
477 if (value.autoLoad and value.load) then
478 Files.load(key)
479 end
480 end
481 end
482end
483
484-- Saves all files with enabled "autoSave"
485-- attribute.
486function Files.save()
487 for _, file in pairs(Files) do
488 if (type(file) == "table") then -- If it's an actual file type instead of a function.
489 if (file.autoSave and file.save) then
490 file.save()
491 end
492 end
493 end
494end
495
496-- Loads the file and all of its required files.
497function Files.load(fileType)
498 if not table.contains(LoadedFiles, fileType) then
499 local loadRequired = Files[fileType].loadRequired
500
501 if (loadRequired) then
502 for _, value in pairs(loadRequired) do
503 Files.load(value)
504 end
505 end
506
507 Files[fileType].load()
508 table.insert(LoadedFiles, fileType)
509 end
510end
511
512-- Saves the table into a file in Graffitis' default format.
513function Files.saveTable(subDir, fileName, tbl)
514 local fileHandle = Files.createDataFile(subDir, fileName, "w")
515
516 for key, value in pairs(tbl) do
517 fileHandle.writeLine(key .. "=" .. tostring(value))
518 end
519
520 if fileHandle then
521 fileHandle.close()
522 end
523end
524
525function Files.loadTable(subDir, fileName)
526 local fileHandle = Files.getDataFileHandle(subDir, fileName, "r", false)
527 local ret = {}
528
529 if not fileHandle then
530 log("Can't get file handle of file \"" .. fileName .. "\"! Returning nil.", "WARNING")
531 return nil
532 end
533
534 repeat
535 local line = fileHandle.readLine()
536
537 if (line ~= nil and line ~= "" and string.find(line, "=")) then
538 local key, value = splitAt(line, "=")
539 ret[key] = value
540 end
541 until line == nil
542
543 if fileHandle then
544 fileHandle.close()
545 end
546
547 return ret
548end
549
550-- Removes the content of a file.
551function Files.clear(subDir, fileName)
552 local fileHandle = Files.getDataFileHandle(subDir, fileName, "w", false)
553
554 if fileHandle then
555 fileHandle.close()
556 end
557end
558
559-- Returns the path to the sub-directory and
560-- creates the folders if necessary.
561function Files.getSubDirPath(subDir)
562 assert(subDir)
563
564 local subDirPath = dataFolderPath
565
566 for _, dir in pairs(subDir) do
567 subDirPath = fs.combine(subDirPath, dir)
568
569 if (fs.exists(subDirPath)) then
570 if (not fs.isDir(subDirPath)) then
571 error("Can't create directory " .. dir .. "! A file with that name exists!", 2)
572 end
573 else
574 fs.makeDir(subDirPath)
575 end
576 end
577
578 return subDirPath
579end
580
581-- Returns the handle of an existing file or null.
582-- The file will automatically be created if
583-- autoCreate is true.
584function Files.getDataFileHandle(subDir, filename, mode, autoCreate)
585 assert(filename)
586 assert(mode)
587
588 local ret
589 local path
590
591 -- Create the path to the file if it doesn't exist.
592 if subDir then
593 local subDirPath
594
595 if (type(subDir) == "string" and subDir ~= "") then
596 subDirPath = fs.combine(dataFolderPath, subDir)
597 path = fs.combine(subDirPath, filename)
598
599 if (fs.exists(subDirPath)) then
600 if (not fs.isDir(subDirPath)) then
601 error("Can't create directory " .. subDir .. "! A file with that name exists!", 2)
602 end
603 else
604 fs.makeDir(subDirPath)
605 end
606 elseif (type(subDir) == "table") then
607 local subDirPath = Files.getSubDirPath(subDir)
608 path = fs.combine(subDirPath, filename)
609 end
610 else
611 path = fs.combine(dataFolderPath, filename)
612 end
613
614 if (fs.exists(path) and fs.isReadOnly(path)) then
615 error("File " .. filePath .. " is readonly!", 2)
616 end
617
618 if (fs.exists(path) or mode == "w") then
619 ret = fs.open(path, mode)
620 elseif autoCreate then
621 ret = fs.open(path, "w") or error("Can't open file " .. path, 2)
622 end
623
624 return ret
625end
626
627-- Creates or overrides a file and returns its handle.
628function Files.createDataFile(subDir, filename)
629 return Files.getDataFileHandle(subDir, filename, "w")
630end
631
632-- Renames the file to the "newName"-parameter.
633-- Note to myself: Don't copy the "get the subDir"-part from this one.
634function Files.rename(subDir, filename, newName)
635 assert(subDir)
636 assert(filename)
637 assert(newName)
638
639 local folderPath
640 local filePath
641
642 if subDir then
643 local subDirPath
644
645 if (type(subDir) == "string" and subDir ~= "") then
646 subDirPath = fs.combine(dataFolderPath, subDir)
647 folderPath = subDirPath
648 filePath = fs.combine(subDirPath, filename)
649
650 if (fs.exists(subDirPath)) then
651 if (not fs.isDir(subDirPath)) then
652 error("Can't access directory " .. subDir .. "! A file with that name exists!", 2)
653 end
654 else
655 return
656 end
657 elseif (type(subDir) == "table") then
658 folderPath = Files.getSubDirPath(subDir)
659 filePath = fs.combine(folderPath, filename)
660 end
661 else
662 folderPath = dataFolderPath
663 filePath = fs.combine(dataFolderPath, filename)
664 end
665
666 fs.move(filePath, fs.combine(folderPath, newName))
667
668 log("Renamed window \"" .. filename .. "\" to \"" .. newName .. "\".")
669end
670
671-- Removes the file.
672-- NOTE: The path accessing is slightly different.
673function Files.remove(subDir, filename)
674 local path
675
676 if subDir then
677 local subDirPath
678
679 if (type(subDir) == "string" and subDir ~= "") then
680 subDirPath = fs.combine(dataFolderPath, subDir)
681 path = fs.combine(subDirPath, filename)
682
683 if (fs.exists(subDirPath)) then
684 if (not fs.isDir(subDirPath)) then
685 error("Can't access directory " .. subDir .. "! A file with that name exists!", 2)
686 end
687 else
688 return
689 end
690 elseif (type(subDir) == "table") then
691 local subDirPath = Files.getSubDirPath(subDir)
692 path = fs.combine(subDirPath, filename)
693 end
694 else
695 path = fs.combine(dataFolderPath, filename)
696 end
697
698 fs.delete(path)
699
700 log("Deleted window \"" .. path .. "\".")
701end
702
703-- >> Version File
704Files.Version = {
705 autoSave = true;
706 autoLoad = true;
707 subDir = nil;
708 loadRequired = { "Project" };
709 name = "version"
710}
711
712function Files.Version.save()
713 local fileHandle = Files.getDataFileHandle(Files.Version.subDir, Files.Version.name, "w")
714
715 if fileHandle then
716 fileHandle.writeLine(version)
717 fileHandle.close()
718 end
719end
720
721function Files.Version.load()
722 local fileHandle = Files.getDataFileHandle(Files.Version.subDir, Files.Version.name, "r", false)
723
724 if fileHandle then
725 local fileVersion = fileHandle.readLine()
726
727 if (fileVersion ~= version) then
728 Converter.run(fileVersion)
729 end
730
731 fileHandle.close()
732 else
733 Converter.run()
734 end
735end
736
737-- >> Project File
738
739Files.Project = {
740 autoSave = true;
741 autoLoad = true;
742 subDir = "Projects"; -- Has to be a string.
743 loadRequired = { "Settings" };
744 extension = ".window";
745}
746
747-- Saves the content of the Windows-table into the
748-- save file.
749function Files.Project.save()
750 for name, window in pairs(Windows.Children) do
751 if (type(window) == "table") then
752 local saveString = Files.serialize(window)
753 local fileHandle = Files.createDataFile({ Files.Project.subDir, currentProject }, name .. Files.Project.extension )
754
755 fileHandle.write(saveString)
756 fileHandle.close()
757 end
758 end
759end
760
761-- Loads the save file and puts the content into
762-- the Windows-table
763function Files.Project.load()
764 local path = fs.combine(dataFolderPath, Files.Project.subDir)
765 path = fs.combine(path, currentProject)
766
767 if not path or not fs.isDir(path) then
768 return
769 end
770
771 for _, filename in pairs(fs.list(path)) do
772 local filePath = fs.combine(path, filename)
773 local windowName = string.sub(filename, 1, #filename - #Files.Project.extension)
774
775 if (not fs.isDir(filePath) and Files.endsWith(filename, Files.Project.extension)) then
776 local fileHandle = Files.getDataFileHandle({ Files.Project.subDir, currentProject }, filename, "r", false)
777
778 if fileHandle then
779 local loadString = fileHandle.readAll()
780 loadString = Files.unserialize(loadString)
781
782 if (loadString ~= nil and loadString ~= "") then
783 Windows.Children[windowName] = loadString
784 end
785
786 fileHandle.close()
787 end
788 end
789 end
790end
791
792-- >> Settings File
793
794Files.Settings = {
795 autoSave = true;
796 autoLoad = true;
797 subDir = nil;
798 name = "Graffiti.cfg";
799}
800
801function Files.Settings.init()
802 if not Settings then
803 return
804 end
805
806 for key, value in pairs(Settings) do
807 if (key == "startupProject") then
808 currentProject = value
809 elseif (key == "language") then
810 Settings.language = value
811 elseif (key == "colorTheme" and ColorThemes[value]) then
812 ObjectColors = ColorThemes[value]
813 end
814 end
815end
816
817-- Creates the settings file in the data-folder and fills it with default values.
818function Files.Settings.save()
819 Files.saveTable(Files.Settings.subDir, Files.Settings.name, Settings)
820end
821
822-- Loads the settings file.
823function Files.Settings.load()
824 local tbl = Files.loadTable(Files.Settings.subDir, Files.Settings.name)
825
826 if tbl then
827 for key, value in pairs(tbl) do
828 Settings[key] = value
829 end
830 end
831
832 Files.Settings.init()
833end
834
835-- >> Language File
836
837function loadLanguage(language)
838 assert(language)
839
840 if not Languages[language] then
841 --log("Tried to load non-existent language " .. tostring(language) .. ".", "WARNING")
842 return
843 end
844
845 for key, value in pairs(Languages[language]) do
846 Text[key] = value
847 end
848
849 --log("Loaded language \"" .. language .. "\".")
850end
851
852Files.Language = {
853 autoSave = true;
854 autoLoad = true;
855 subDir = "Language";
856 extension = ".lang";
857 loadRequired = { "Settings" };
858}
859
860function Files.Language.save()
861 local fileName = "Graffiti." .. Settings.language .. Files.Language.extension
862 Files.saveTable(Files.Language.subDir, fileName, Text)
863end
864
865function Files.Language.load()
866 local path = fs.combine(dataFolderPath, Files.Language.subDir)
867
868 if fs.exists(path) and fs.isDir(path) then
869 for _, filename in pairs(fs.list(path)) do
870 local fullPath = fs.combine(path, filename)
871
872 if not (fs.isDir(fullPath) and
873 filename:startsWith("Graffiti.") and
874 filename:endsWith(".lang")) then
875 -- Gets the part between "Graffiti." and ".lang"
876 -- e.g. "Graffiti.en-US.lang" -> "en-US"
877 local languageDesc = filename:match("%.(.+)%.")
878 local languageTable = Files.loadTable(Files.Language.subDir, filename)
879
880 Languages[languageDesc] = languageTable
881 end
882 end
883 end
884
885 loadLanguage(Settings.language)
886end
887
888-- >> Log File
889Files.Log = {
890 autoSave = false;
891 autoLoad = false;
892 subDir = "Log";
893 name = "Graffiti.log";
894}
895
896-- Writes the text into a logfile.
897function log(text, logType)
898 if not doLog or not logFileLoaded then
899 return
900 end
901
902 local logFileHandle = Files.getDataFileHandle(Files.Log.subDir, Files.Log.name, "a", true)
903
904 if (type(text) == "table") then
905 local delimiter = ""
906
907 logFileHandle.write((logType or "INFO") .. ": ")
908
909 for key, value in pairs(text) do
910 logFileHandle.write(delimiter .. key .. "=" .. tostring(value))
911 delimiter = ", "
912 end
913
914 logFileHandle.writeLine()
915 else
916 logFileHandle.writeLine((logType or "INFO") .. ": " .. tostring(text))
917 end
918
919 logFileHandle.close()
920end
921
922-- >> Converter
923
924function Converter.updateObject(from, to, object)
925 if object.isContainer or object.children or object.Children then
926 Converter.updateContainer(from, to, object)
927 else
928 -- Object conversion goes here.
929 end
930end
931
932function Converter.updateContainer(from, to, container)
933 if container.children then -- v1.7: The "children" attribute has been renamed to "Children"
934 container.Children = container.children
935 container.children = nil
936 end
937
938 for objectKey, object in pairs(container.Children) do
939 Converter.updateObject(from, to, object)
940 end
941end
942
943-- Checks all variables and tables for data from
944-- earlier versions which could make it
945-- incompatible with the newest version and
946-- updates those if possible.
947function Converter.run(oldVersion)
948 log("Running converter...")
949 log("Old version: " .. tostring(oldVersion) .. ".", "ATTR")
950 log("New version: " .. tostring(version) .. ".", "ATTR")
951
952 Converter.updateContainer(oldVersion, version, Windows)
953end
954
955-- >>> Shortcut functions (for key inputs)
956-- Not yet implemented! WIP!
957-- TODO: Fix bug where the key event won't disappear from the queue.
958
959function callShortcut(key)
960 --[[if (key and Shortcuts[key]) then
961 Shortcuts[key]()
962 end]]
963end
964
965-- S
966Shortcuts[31] = function()
967 out.clear()
968 out.setCursorPos(1, 1)
969 print("Saving windows...")
970
971 Files.Project.save()
972
973 print("Windows saved!")
974 print("Press any key to continue...")
975end
976
977-- Q
978Shortcuts[16] = function()
979 quit = true
980end
981
982-- >>> Object helper functions
983
984function clearScreen()
985 out.setBackgroundColor(ObjectColors.background)
986 out.setTextColor(ObjectColors.text)
987 out.clear()
988 out.setCursorPos(1, 1)
989end
990
991-- Returns whether the button with the given name
992-- has been pressed.
993function defaultButtonPressed(name, x, y)
994 assert(name)
995 assert(x)
996 assert(y)
997
998 local window = getCurrentWindow()
999
1000 if (DefaultButtons[name]) then
1001 local button = DefaultButtons[name]
1002 if (x >= button.left and x <= button.right and y >= button.top and y <= button.bottom) then
1003 return (button.required() or (editMode and not showCustomWindow))
1004 end
1005 else
1006 return false
1007 end
1008end
1009
1010-- Returns the table containing the windows that
1011-- should be displayed.
1012function getWindowContainer()
1013 if showCustomWindow then
1014 return CustomWindows[showCustomWindow]
1015 else
1016 return Windows
1017 end
1018end
1019
1020-- Returns the window object that is currently
1021-- displayed.
1022function getCurrentWindow()
1023 local windowContainer = getWindowContainer()
1024
1025 return windowContainer.Children[currentWindow]
1026end
1027
1028-- Used by the List and Selector objects to
1029-- determine how wide the list should be.
1030function getLongestString(strings)
1031 if (strings == nil or #strings == 0) then
1032 return 0
1033 end
1034
1035 local ret = 0
1036 for key, value in pairs(strings) do
1037 length = string.len(value)
1038 if (length > ret) then
1039 ret = length
1040 end
1041 end
1042
1043 return ret
1044end
1045
1046-- Checks whether dir is a valid direction-string.
1047function isValidDirection(dir)
1048 if (dir ~= nil and
1049 (dir == "left" or
1050 dir == "up" or
1051 dir == "right" or
1052 dir == "down")) then
1053 return true
1054 end
1055
1056 return false
1057end
1058
1059--[[ Returns a table containing tables for all
1060files and directories in the given path:
1061Returns: fileName, filePath, isDir
1062]]
1063function getFileList(path)
1064 local ret = {}
1065 local dirList = {}
1066 local fileList = {}
1067
1068 for file in fs.list(path) do
1069 if (fs.isDir(file)) then
1070 table.insert(dirList, dirIndex, file)
1071 dirIndex = dirIndex + 1
1072 else
1073 table.insert(fileList, fileIndex, file)
1074 fileIndex = fileIndex + 1
1075 end
1076 end
1077 for _, file in ipairs(dirList) do
1078 table.insert(list, {fileName=file, filePath=fs.combine(path, file), isDir=true})
1079 end
1080 for _, file in ipairs(fileList) do
1081 table.insert(list, {fileName=file, filePath=fs.combine(path, file), isDir=false})
1082 end
1083
1084 return ret
1085end
1086
1087-- Returns "horizontal" or "vertical" depending on
1088-- the given direction.
1089function getOrientation(direction)
1090 local orientation = nil
1091
1092 if (direction == "left" or direction == "right") then
1093 orientation = "horizontal"
1094 elseif (direction == "up" or direction == "down") then
1095 orientation = "vertical"
1096 end
1097
1098 return orientation
1099end
1100
1101-- Checks whether the eventTypes table contains
1102-- the given event type.
1103function eventTypeExists(eventType)
1104 if (eventType == nil) then
1105 return true
1106 end
1107
1108 for _, event in pairs(EventTypes) do
1109 if (event == eventType) then
1110 return true
1111 end
1112 end
1113
1114 return false
1115end
1116
1117-- Returns the size that a buffer needs to have
1118-- to contain all children of a container.
1119function getNecessaryBufferSize(Children, minWidth, minHeight)
1120 local width, height = (minWidth or 0), (minHeight or 0)
1121
1122 for _, child in pairs(Children) do
1123 local right = child.width and child.x + child.width or child.x
1124 local bottom = child.height and child.y + child.height or child.y
1125
1126 width = (right > width) and right or width
1127 height = (bottom > height) and bottom or height
1128 end
1129
1130 return width, height
1131end
1132
1133-- Returns a table containing the position and
1134-- size that the scroll bar of a ScrollView object
1135-- should have.
1136function getScrollBarInfo(pos, containerSize, bufferSize)
1137 local ret = {}
1138 local maxScrollBarHeight = containerSize - 4
1139
1140 local scrollCount = bufferSize - containerSize + 1 -- How often can the user scroll?
1141 if (scrollCount < 0) then
1142 scrollCount = 0
1143 end
1144
1145 local scrollBarSize = math.ceil(maxScrollBarHeight * (1 / (scrollCount + 1)))
1146 local scrollBarPos
1147 if (scrollCount == 0) then
1148 scrollBarPos = 0
1149 --elseif (pos == scrollCount) then
1150 --scrollBarPos = maxScrollBarHeight - scrollBarSize
1151 else
1152 scrollBarPos = math.floor((maxScrollBarHeight / (scrollCount + 1)) * pos)
1153 end
1154
1155 ret.size = scrollBarSize
1156 ret.pos = scrollBarPos
1157
1158 return ret
1159end
1160
1161-- >>> Path
1162
1163Path = {}
1164
1165-- Returns the container which is at the paths
1166-- nest level.
1167function Path:getContainerAt(nestLevel)
1168 local container = getCurrentWindow()
1169
1170 for i = 1, nestLevel do
1171 if (container.Children[self[i]] and container.Children[self[i]].isContainer) then
1172 container = container.Children[self[i]]
1173 else
1174 return nil -- NOTE: was "break" before
1175 end
1176 end
1177
1178 return container
1179end
1180
1181-- Returns the object which is represented by the
1182-- path.
1183function Path:getObject()
1184 local container = Path.getContainerAt(self, #self - 1)
1185 return container.Children[self[#self]]
1186end
1187
1188-- Returns the coordinates which are relative to
1189-- the last container represented by the path.
1190function Path:getRelativePos(x, y, checkFullPath)
1191 --log("Path.getRelativePos", "FUNC")
1192 --log("x: " .. x .. ", y: " .. y .. ", checkFullPath: " .. tostring(checkFullPath) .. ".", "ATTR")
1193
1194 local container
1195 local limit = checkFullPath and #self or #self - 1
1196
1197 for i = 1, limit do
1198 container = Path.getContainerAt(self, i)
1199
1200 if container then
1201 x, y = Objects.Container.getRelativePos(container, x, y)
1202 else
1203 break
1204 end
1205 end
1206
1207 return x, y
1208end
1209
1210--[[ Generates a table of keys representing the
1211nesting of the containers that have been clicked.
1212Returns: e.g. {containeID, subContainerID}]]
1213Path.getContainerPath = function(x, y)
1214 local containerPath = {}
1215 local currentContainer = getCurrentWindow()
1216 local finished = false
1217 local nestLevel = 1
1218
1219 while not finished do
1220 finished = true -- Set to true so that the loop stops when no container is found.
1221 for objectID, object in pairs(currentContainer.Children) do
1222 if finished and object.isContainer then
1223 if (Objects.Container.contentAreaClicked(object, x, y)) then
1224 containerPath[nestLevel] = objectID
1225 currentContainer = currentContainer.Children[objectID]
1226 x, y = Objects.Container.getRelativePos(currentContainer, x, y)
1227 finished = false
1228 end
1229 end
1230 end
1231
1232 nestLevel = nestLevel + 1
1233 end
1234
1235 return containerPath
1236end
1237
1238-- >>> Buffer
1239
1240Buffer = {
1241 bufferTable = {};
1242 width = 0;
1243 height = 0;
1244}
1245
1246function Buffer.newPixel(backCol, textCol, char, path)
1247 local ret = {}
1248 ret.background = backCol or ObjectColors.background
1249 ret.text = textCol or ObjectColors.text
1250 ret.char = (type(char) == "string" and char ~= "") and char or " "
1251 ret.path = path or nil
1252 if (backCol or textCol or char ~= " " or path) then
1253 ret.draw = true
1254 else
1255 ret.draw = false
1256 end
1257
1258 return ret;
1259end
1260
1261function Buffer:new(object)
1262 object = object or {}
1263 setmetatable(object, self)
1264 self.__index = self
1265 return object
1266end
1267
1268function Buffer:init(width, height, path, backCol, textCol)
1269 self.width = width or error("Can't initialize buffer because the width didn't get specified!")
1270 self.height = height or error("Can't initialize buffer because the height didn't get specified!")
1271 self.bufferTable = {}
1272
1273 for col = 1, width do
1274 self.bufferTable[col] = {}
1275 for row = 1, height do
1276 self.bufferTable[col][row] = self.newPixel(backCol, textCol, " ", path)
1277 end
1278 end
1279end
1280
1281function Buffer:trim(left, top, right, bottom)
1282 left = (left < 0) and 0 or left
1283 top = (top < 0) and 0 or top
1284 right = (right < 0) and 0 or right
1285 bottom = (bottom < 0) and 0 or bottom
1286
1287 local width = self.width - left - right
1288 local height = self.height - top - bottom
1289 local trimmed = Buffer:new()
1290 trimmed:init(width, height)
1291
1292 for col = 1, width do
1293 for row = 1, height do
1294 trimmed:setPixel(col, row, self.bufferTable[left + col][top + row])
1295 end
1296 end
1297
1298 return trimmed
1299end
1300
1301function Buffer:draw(x, y)
1302 x = x or 1
1303 y = y or 1
1304 local currentPixel
1305
1306 for col = 0, self.width - 1 do
1307 for row = 0, self.height - 1 do
1308 currentPixel = self.bufferTable[col+1][row+1]
1309 if (currentPixel.draw) then
1310 out.setCursorPos(x + col, y + row)
1311 out.setBackgroundColor(currentPixel.background)
1312 out.setTextColor(currentPixel.text)
1313 out.write(currentPixel.char)
1314 currentPixel.draw = false
1315 end
1316 end
1317 end
1318end
1319
1320function Buffer:setBackgroundColor(color)
1321 for col = 1, width do
1322 for row = 1, height do
1323 self.bufferTable[col][row].background = color
1324 end
1325 end
1326end
1327
1328function Buffer:setTextColor(color)
1329 for col = 1, width do
1330 for row = 1, height do
1331 self.bufferTable[col][row].text = color
1332 end
1333 end
1334end
1335
1336function Buffer:setPixel(x, y, pixel)
1337 assert(x)
1338 assert(y)
1339 assert(pixel)
1340
1341 if (x > 0 and x <= self.width and y > 0 and y <= self.height) then
1342 for key, value in pairs(pixel) do
1343 if value then
1344 self.bufferTable[x][y][key] = value
1345 end
1346 end
1347 end
1348end
1349
1350function Buffer:addText(x, y, text)
1351 assert(x)
1352 assert(y)
1353 assert(text)
1354
1355 for char in tostring(text):gmatch(".") do
1356 self:setPixel(x, y, { char=char })
1357 x = x + 1
1358 end
1359end
1360
1361function Buffer:addBuffer(x, y, buffer)
1362 assert(x)
1363 assert(y)
1364 assert(buffer)
1365 for col = 1, buffer.width do
1366 for row = 1, buffer.height do
1367 self:setPixel(x + col - 1, y + row - 1, buffer.bufferTable[col][row])
1368 end
1369 end
1370end
1371
1372-- Adds a border to the container buffer.
1373function Buffer:makeBorder(path, color)
1374 local width, height = self.width, self.height
1375
1376 self:addBuffer(1, 1, Objects.Line.get("horizontal", width, color, path))
1377 self:addBuffer(1, height, Objects.Line.get("horizontal", width, color, path))
1378 self:addBuffer(1, 1, Objects.Line.get("vertical", height, color, path))
1379 self:addBuffer(width, 1, Objects.Line.get("vertical", height, color, path))
1380end
1381
1382-- >>> Objects
1383
1384-- Returns a new object with its default attributes
1385-- depending on the object type.
1386-- Note: Don't call this function, use Objects.create!
1387function Objects.new(objectType, x, y)
1388 assert(objectType)
1389 assert(x)
1390 assert(y)
1391
1392 log("Creating new object. Type: " .. objectType .. ", position: " .. x .. ", " .. y .. ".", "INFO")
1393
1394 local object = {}
1395 local path = Path.getContainerPath(x, y)
1396 local relativeX, relativeY = Path.getRelativePos(path, x, y, true)
1397
1398 object.objID = "new" .. objectType
1399 object.objType = objectType
1400 object.path = path
1401 local modX, modY = Objects.getPosModifier(object, true)
1402 object.x = relativeX + (modX * -1)
1403 object.y = relativeY + (modY * -1)
1404 object.absoluteX = x + (modX * -1)
1405 object.absoluteY = y + (modY * -1)
1406
1407 local maxWidth = maxX - x
1408 local maxHeight = maxY - y
1409
1410 if (Objects[objectType] and Objects[objectType].new) then
1411 Objects[objectType].new(object, maxWidth, maxHeight)
1412 elseif (Objects.Container[objectType]) then
1413 Objects.Container.new(object, maxWidth, maxHeight)
1414 else
1415 Objects.Unknown.new(objectType)
1416 end
1417
1418 return object
1419end
1420
1421-- Creates a new object at the given position with
1422-- its default attributes.
1423function Objects.create(objType, x, y)
1424 local object = Objects.new(objType, x, y)
1425 local container = Path.getContainerAt(object.path, #object.path)
1426 local key = Objects.Container.getNextFreeKey(container)
1427 table.insert(object.path, key)
1428 table.insert(container.Children, key, object)
1429
1430 if not showCustomWindow then
1431 WindowBuffer:addBuffer(x, y, Objects.get(object))
1432 Objects.draw(object)
1433 end
1434
1435 return object
1436end
1437
1438-- Creates the default object and changes the
1439-- specified attributes to the given ones.
1440function Objects.createCustom(objType, x, y, attributes)
1441 local object = Objects.create(objType, x, y)
1442
1443 for key, value in pairs(attributes) do
1444 object[key] = value
1445 end
1446
1447 return object
1448end
1449
1450function Objects.init()
1451 log("Initializing Objects...", "INFO")
1452
1453 for _, window in pairs(Windows.Children) do
1454 for _, object in pairs(window.Children) do
1455 if (type(object) == "table") then
1456 local objType = object.objType
1457
1458 if (Objects[objType] and Objects[objType].init) then
1459 log("Initializting " .. objType .. " " .. object.objID .. ".", "INFO")
1460 Objects[objType].init(object)
1461 elseif (object.isContainer) then
1462 Objects.Container.init(object)
1463 end
1464 end
1465 end
1466 end
1467end
1468
1469-- Returns the x and y values which should be
1470-- added to an objects' position to match the
1471-- modifications of its parents (e.g. ScrollView)
1472function Objects.getPosModifier(self, checkFullPath)
1473 assert(self)
1474
1475 local x, y = 0, 0
1476 local maxNestLevel = checkFullPath and #self.path or #self.path - 1
1477
1478 if (maxNestLevel > 0) then
1479 for nestLevel = 1, maxNestLevel do
1480 local object = Path.getContainerAt(self.path, nestLevel)
1481 local modX, modY = Objects.Container.getPosModifier(object, x, y)
1482 x, y = x + modX, y + modY
1483 end
1484 end
1485
1486 return x, y
1487end
1488
1489-- Returns the relative position of the object
1490-- including the possible modifications of its
1491-- containers.
1492function Objects.getRelativePos(self)
1493 assert(self)
1494
1495 local x, y = self.x, self.y
1496 local modX, modY = Objects.getPosModifier(self)
1497
1498 return x + modX, y + modY
1499end
1500
1501-- Returns the absolute position of the object
1502-- including the possible modifications of its
1503-- containers.
1504function Objects.getAbsolutePos(self)
1505 assert(self)
1506
1507 local x, y = self.absoluteX, self.absoluteY
1508 local modX, modY = Objects.getPosModifier(self)
1509
1510 return x + modX, y + modY
1511end
1512
1513function Objects.isClicked(self, x, y)
1514 assert(self)
1515 assert(x)
1516 assert(y)
1517
1518 return (x >= self.absoluteX and
1519 x <= self.absoluteX + self.width - 1 and
1520 y >= self.absoluteY and
1521 y <= self.absoluteY + self.height - 1)
1522end
1523
1524-- Returns the buffer of the object.
1525function Objects.get(self, param)
1526 assert(self)
1527
1528 --log("Getting " .. self.objType .. " object. ID: \"" .. self.objID .. "\", param: \"" .. tostring(param) .. "\"", "INFO")
1529
1530 if (not editMode and not param and self.load) then
1531 if UserData[self.load] then
1532 param = UserData[self.load]()
1533 log("Loaded value of " .. self.objType .. " \"" .. self.objID .. "\":" .. param .. ".")
1534 end
1535 end
1536
1537 local objType = self.objType or "Unknown"
1538 if (self.isContainer) then
1539 return Objects.Container.get(self, param)
1540 elseif (Objects[objType] and Objects[objType].get) then
1541 return Objects[objType].get(self, param)
1542 else
1543 error("Can't get buffer of object type \"" .. objType .. "\"!")
1544 end
1545end
1546
1547--[[ Used to return the buffer of an object which
1548shows the user where to click to interact with
1549the object in a certain way.
1550(e.g. scrolling the ScrollView)
1551]]
1552function Objects.addMarker(self, buffer)
1553 assert(self)
1554
1555 log("Objects.addMarker", "FUNC")
1556 log(self.objType .. " ID: \"" .. self.objID .. "\".", "ATTR")
1557
1558 if (Objects[self.objType] and Objects[self.objType].addMarker) then
1559 Objects[self.objType].addMarker(self, buffer)
1560 elseif (Objects.Container[self.objType] and Objects.Container[self.objType].addMarker) then
1561 Objects.Container[self.objType].addMarker(self, buffer)
1562 end
1563end
1564
1565function Objects.remove(self)
1566 assert(self)
1567
1568 log("Objects.remove", "FUNC")
1569 log(self.objType .. " ID: \"" .. self.objID .. "\".")
1570
1571 local path = self.path
1572 local objKey = path[#path]
1573 local container = Path.getContainerAt(self.path, #self.path - 1)
1574
1575 container.Children[objKey] = nil
1576
1577 for i = #path - 2, 0, -1 do
1578 local parentContainer = Path.getContainerAt(self.path, i)
1579 parentContainer.Children[path[i + 1]] = container
1580 container = parentContainer
1581 end
1582
1583 Windows.Children[currentWindow] = container
1584end
1585
1586-- Draws the object.
1587function Objects.draw(self, param, drawMarker)
1588 assert(self)
1589
1590 log("Drawing " .. self.objType .. ". ID: " .. self.objID .. ", param: \"" .. tostring(param) .. "\"", "ATTR")
1591
1592 local buffer = Objects.get(self, param)
1593 if drawMarker then
1594 Objects.addMarker(self, buffer)
1595 end
1596
1597 if (#self.path > 1) then
1598 local container = getCurrentWindow()
1599 local x, y = self.absoluteX, self.absoluteY
1600 Objects.Container.drawBuffer(container, buffer, self.path, x, y, x, y, 0)
1601 else
1602 buffer:draw(self.x, self.y)
1603 end
1604
1605 term.setBackgroundColor(ObjectColors.background)
1606 term.setTextColor(ObjectColors.text)
1607 drawDefaultButtons()
1608end
1609
1610-- Returns the resulting event and a "params"-array.
1611function Objects.click(self, x, y)
1612 assert(self)
1613
1614 log(self.objType .. " \"" .. self.objID .. "\" clicked at " .. x .. ", " .. y .. ".", "INFO")
1615
1616 local objType = self.objType
1617 local event, params
1618 if Objects[objType] and Objects[objType].click then
1619 event, params = Objects[objType].click(self, x, y)
1620 elseif (self.isContainer and Objects.Container[objType] and Objects.Container[objType].click) then
1621 event, params = Objects.Container[objType].click(self, x, y)
1622 end
1623
1624 return event, params
1625end
1626
1627-- Returns the resulting event and a "params"-array.
1628function Objects.drag(self, x, y)
1629 assert(self)
1630
1631 log(self.objType .. " \"" .. self.objID .. "\" dragged at " .. x .. ", " .. y .. ".", "INFO")
1632
1633 local objType = self.objType
1634 local event, params
1635 if Objects[objType] and Objects[objType].drag then
1636 event, params = Objects[objType].drag(self, x, y)
1637 elseif (self.isContainer and Objects.Container[objType] and Objects.Container[objType].drag) then
1638 event, params = Objects.Container[objType].drag(self, x, y)
1639 end
1640
1641 return event, params
1642end
1643
1644-- Gets called when an object gets clicked in edit
1645-- mode. Returns false if the object should be
1646-- edited as usual (move, scale).
1647function Objects.editorClick(self, x, y)
1648 assert(self)
1649 assert(x)
1650 assert(y)
1651
1652 log("Objects.editorClick", "FUNC")
1653 log(self.objType .. " ID: \"" .. self.objID .. "\", x: " .. x .. ", y: " .. y .. ".", "ATTR")
1654
1655 if (Objects[self.objType] and Objects[self.objType].editorClick) then
1656 return Objects[self.objType].editorClick(self, x, y)
1657 elseif (Objects.Container[self.objType] and Objects.Container[self.objType].editorClick) then
1658 return Objects.Container[self.objType].editorClick(self, x, y)
1659 else
1660 return false
1661 end
1662end
1663
1664-- Moves the object.
1665function Objects.move(self, addX, addY)
1666 assert(self)
1667
1668 log("Objects.move", "FUNC")
1669 log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
1670
1671 local objType = self.objType
1672
1673 if (Objects[objType] and Objects[objType].move) then
1674 Objects[objType].move(self, addX, addY)
1675 elseif (self.isContainer) then
1676 self.x = self.x + addX
1677 self.y = self.y + addY
1678 Objects.Container.move(self, addX, addY)
1679 else
1680 self.x = self.x + addX
1681 self.y = self.y + addY
1682 self.absoluteX = self.absoluteX + addX
1683 self.absoluteY = self.absoluteY + addY
1684 end
1685end
1686
1687-- Scales the object.
1688function Objects.scale(self, x, y)
1689 assert(self)
1690
1691 log("Objects.scale", "FUNC")
1692 log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
1693 log("Object pos: " .. self.x .. ", " .. self.y, "DEBUG")
1694
1695 local objType = self.objType
1696
1697 if (Objects[objType] and Objects[objType].scale) then
1698 Objects[objType].scale(self, x, y)
1699 elseif (self.isContainer and Objects.Container[objType].scale) then
1700 Objects.Container[objType].scale(self, x, y)
1701 else
1702 local objX, objY = self.x, self.y
1703 if (x > objX and y >= objY) then
1704 self.width = x - objX + 1
1705 self.height = y - objY + 1
1706 end
1707 end
1708end
1709
1710--[[ Returns the position of the pixel which shows
1711the user where to click when he wants to move the
1712object.]]
1713function Objects.getMovePos(self)
1714 assert(self)
1715
1716 local objType = self.objType
1717 if (Objects[objType] and Objects[objType].getMovePos) then
1718 return Objects[objType].getMovePos(self)
1719 elseif (self.isContainer and Objects.Container[objType] and Objects.Container[objType].getMovePos) then
1720 return Objects.Container[objType].getMovePos(self)
1721 else
1722 return Objects.getAbsolutePos(self)
1723 end
1724end
1725
1726--[[ Returns the position of the pixel which shows
1727the user where to click when he wants to scale the
1728object.]]
1729function Objects.getScalePos(self)
1730 if not self.canScale then
1731 error("Tried to get scale pos of an unscalable object!")
1732 end
1733
1734 local objType = self.objType
1735 if (Objects[objType] and Objects[objType].getScalePos) then
1736 return Objects[objType].getScalePos(self)
1737 elseif (self.isContainer and Objects.Container[objType].getScalePos) then
1738 Objects.Container[objType].getScalePos(self)
1739 else
1740 local x, y = Objects.getAbsolutePos(self)
1741 return x + self.width - 1, y + self.height - 1
1742 end
1743end
1744
1745-- Returns the left, top, right and bottom coordinates of the object.
1746function Objects.getDimensions(self)
1747 assert(self)
1748
1749 local objType = self.objType
1750 if (Objects[objType] and Objects[objType].getDimensions) then
1751 return Objects[objType].getDimensions(self)
1752 elseif (self.isContainer and Objects.Container[objType] and Objects.Container[objType].getDimensions) then
1753 return Objects.Container[objType].getDimensions(self)
1754 else
1755 local left, top, right, bottom
1756 left, top = self.x, self.y
1757 right = left + self.width - 1
1758 bottom = top + self.height - 1
1759 return left, top, right, bottom
1760 end
1761end
1762
1763function Objects.attributeChanged(self, key, oldValue, newValue)
1764 assert(self)
1765
1766 local objType = self.objType
1767 if (Objects[objType] and Objects[objType].attributeChanged) then
1768 Objects[objType].attributeChanged(self, key, oldValue, newValue)
1769 elseif (self.isContainer and Objects.Container[objType] and Objects.Container[objType].attributeChanged) then
1770 Objects.Container[objType].attributeChanged(self, key, oldValue, newValue)
1771 end
1772end
1773
1774function Objects.editAttributes(self)
1775 assert(self)
1776
1777 log("Editing attributes of " .. self.objType .. " \"" .. self.objID .. "\".", "INFO")
1778
1779 local objType = self.objType
1780 local attributes
1781
1782 if (Objects[objType] and Objects[objType].attributes) then
1783 attributes = Objects[objType].attributes
1784 elseif (Objects.Container[objType] and Objects.Container[objType].attributes) then
1785 attributes = Objects.Container[objType].attributes
1786 else
1787 attributes = {}
1788 end
1789
1790 local finished = false
1791 while not finished do
1792 local yPos = 2
1793 local top = yPos
1794 clearScreen()
1795
1796 for index = 1, #attributes do
1797 local description = attributes[index].description
1798 local value = self[attributes[index].attrName]
1799 local required = attributes[index].required
1800
1801 out.setCursorPos(2, yPos)
1802
1803 local requirementMet = false
1804
1805 if required then
1806 local reqAttrName = required.attrName
1807 local reqValue = required.value
1808
1809 for reqAttrKey, reqAttributeValue in pairs(self) do
1810 if (reqAttrKey == reqAttrName and reqAttributeValue == reqValue) then
1811 requirementMet = true
1812 end
1813 end
1814 end
1815
1816 if required and not requirementMet then
1817 out.setBackgroundColor(ObjectColors.disabled)
1818 out.setTextColor(ObjectColors.disabledText)
1819 end
1820
1821 out.write(description .. ": " .. tostring(value))
1822
1823 if required and not requirementMet then
1824 out.setBackgroundColor(ObjectColors.background)
1825 out.setTextColor(ObjectColors.text)
1826 end
1827
1828 yPos = yPos + 1
1829 end
1830
1831 drawSimpleButton(2, yPos + 1, Text.done)
1832 out.setBackgroundColor(ObjectColors.background)
1833
1834 local bottom = yPos - 1
1835 local x, y, mouseButton = getCursorInput()
1836
1837 if y >= top and y <= bottom then
1838 local selectedAttrIndex = y - top + 1
1839 local attribute = attributes[selectedAttrIndex]
1840 local attrName = attributes[selectedAttrIndex].attrName
1841 local attrType = attribute.attrType
1842 local attrValue = self[attrName]
1843
1844 log("Editing \"" .. attrType .. "\" attribute \"" .. attrName .. "\".", "INFO")
1845
1846 if not outIsTerm then
1847 drawPixel(1, y, colors.yellow)
1848 end
1849
1850 if (attrType == "text") then
1851 if not outIsTerm then
1852 drawPixel(1, y, ObjectColors.Editor.textInputMarker)
1853 end
1854
1855 local left = #attribute.description + 4
1856 local lineWidth = maxX - left + 1
1857
1858 out.setCursorPos(left, y)
1859 out.write(string.rep(" ", lineWidth))
1860 out.setCursorPos(left, y)
1861
1862 local input = readUserInput("Enter a vlaue for the \"" .. attribute.description .. "\" value.")
1863
1864 if input then
1865 self[attrName] = input
1866 end
1867 elseif (attrType == "list") then
1868 local nextAttr = attribute.list[1]
1869 local selectAttribute = false
1870
1871 for _, value in ipairs(attribute.list) do
1872 if (selectAttribute) then
1873 nextAttr = value
1874 break
1875 elseif (attrValue == value) then
1876 selectAttribute = true
1877 end
1878 end
1879
1880 self[attrName] = nextAttr
1881 elseif (attrType == "number") then
1882 if not outIsTerm then
1883 drawPixel(1, y, ObjectColors.Editor.textInputMarker)
1884 end
1885
1886 local left = #attribute.description + 4
1887 local lineWidth = maxX - left + 1
1888
1889 out.setCursorPos(left, y)
1890 out.write(string.rep(" ", lineWidth))
1891 out.setCursorPos(left, y)
1892
1893 local input = readUserInput("Enter a number for the \"" .. attribute.description .. "\" value.")
1894
1895 if input and tonumber(input) then
1896 self[attrName] = tonumber(input)
1897 end
1898 elseif (attrType == "bool") then
1899 self[attrName] = not attrValue
1900 elseif (attrType == "table") then
1901 editMode = false
1902 ListEditorList = {}
1903
1904 if self[attrName] then
1905 for key, value in pairs(self[attrName]) do
1906 ListEditorList[key] = value
1907 end
1908 else
1909 ListEditorList = {}
1910 end
1911
1912 local result = CustomWindows.draw("ListEditor", "Main")
1913
1914 if result then
1915 self[attrName] = {}
1916
1917 for key, value in pairs(ListEditorList) do
1918 self[attrName][key] = value
1919 end
1920 end
1921
1922 editMode = true
1923 currentWindow = lastWindow
1924 end
1925
1926 Objects.attributeChanged(self, attrName, attrValue, self[attrName])
1927 elseif (y == yPos + 1 and x >= 2 and x <= 1 + string.len(Text.done)) then -- Done button
1928 finished = true
1929 end
1930 end
1931end
1932
1933-- >> Unknown
1934Objects.Unknown = {}
1935Objects.Unknown.new = function(objType)
1936 objType = objType and " \"" .. objType .. "\"" or ""
1937 error("Tried to create new object with unknown object ID" .. objType .. "!")
1938end
1939
1940Objects.Unknown.get = function(self)
1941 error("Tried to get the buffer of and unknown object!")
1942end
1943
1944-- >> Button
1945
1946Objects.Button = {}
1947Objects.Button.attributes = {
1948 { description = "Object ID", attrName = "objID", attrType = "text" };
1949 { description = "Text", attrName = "text", attrType = "text" };
1950 { description = "Function type", attrName = "funcType", attrType = "list", list = { "switch", "function", "toggle function" } };
1951 { description = "Window", attrName = "window", attrType = "text", required = { attrName = "funcType", value = "switch" } };
1952}
1953
1954Objects.Button.new = function(self, maxWidth, maxHeight)
1955 self.width = (maxWidth < Size.Button.width) and maxWidth or Size.Button.width
1956 self.height = (maxHeight < Size.Button.height) and maxHeight or Size.Button.height
1957 self.widthPercent = maxX / self.width
1958 self.heightPercent = maxY / self.height
1959 self.text = "Button"
1960 self.funcType = ""
1961 self.window = ""
1962 self.canScale = true
1963 self.canClick = true
1964end
1965
1966Objects.Button.get = function(self, buttonColor)
1967 local width = self.width
1968 local height = self.height
1969 local path = self.path
1970 local text = self.text
1971 local color
1972
1973 if buttonColor then
1974 color = buttonColor
1975 elseif (ObjectData.Button[self.objID]) then
1976 color = ObjectColors.Button.active
1977 else
1978 color = ObjectColors.Button.default
1979 end
1980
1981 local buffer = Buffer:new()
1982 local textCol = math.floor((width - string.len(text)) / 2) + 1
1983 local textRow = math.ceil(height / 2)
1984 buffer:init(width, height, path, color, ObjectColors.Button.text)
1985 buffer:addText(textCol, textRow, text)
1986
1987 return buffer
1988end
1989
1990-- Returns: "button_clicked" event, objID
1991Objects.Button.click = function(self, x, y)
1992 assert(self)
1993
1994 local objID = self.objID
1995 local funcType = self.funcType
1996 local window = self.window
1997 if (funcType == "switch") then
1998 drawWindow(window)
1999 return nil, nil
2000 elseif (funcType == "function" or funcType == "toggle function") then
2001 local state = (funcType == "function" or ObjectData.Button[self.objID] == true)
2002
2003 if (funcType == "function" and changeButtonColor) then
2004 Objects.draw(self, ObjectColors.Button.active)
2005 elseif (funcType == "toggle function") then -- Required because changeButtonColor could be false
2006 state = not state
2007 ObjectData.Button[self.objID] = state
2008 Objects.draw(self)
2009 end
2010
2011 if showCustomWindow and
2012 CustomFunctions[showCustomWindow] and
2013 CustomFunctions[showCustomWindow][objID] then
2014 CustomFunctions[showCustomWindow][objID](state)
2015 elseif (UserData[objID]) then
2016 UserData[objID](state)
2017 end
2018
2019 if (funcType == "function") then
2020 if changeButtonColor then
2021 Objects.draw(self, ObjectColors.Button.default)
2022 else
2023 changeButtonColor = true
2024 end
2025 end
2026
2027 local eventType = (funcType == "function") and "button_clicked" or "button_toggled"
2028
2029 return eventType, {self.objID, state}
2030 else
2031 log("Unknown function type: \"" .. funcType .. "\"", "WARNING")
2032 end
2033end
2034
2035-- >> Text
2036
2037Objects.Text = {}
2038Objects.Text.attributes = {
2039 { description = "Text", attrName = "text", attrType = "text" };
2040}
2041
2042Objects.Text.new = function(self)
2043 self.text = "newText"
2044 self.width = #self.text
2045 self.height = 1
2046end
2047
2048Objects.Text.get = function(self)
2049 local width = #self.text
2050 local buffer = Buffer:new()
2051 buffer:init(width, 1, self.path)
2052 buffer:addText(1, 1, self.text)
2053
2054 return buffer
2055end
2056
2057Objects.Text.draw = function(self)
2058 local x = self.x
2059 local y = self.y
2060 local text = self.text
2061 out.setCursorPos(x, y)
2062 out.write(text)
2063end
2064
2065Objects.Text.getDimensions = function(self)
2066 local left, top, right, bottom = self.x, self.y, 1, self.y
2067 right = left + string.len(self.text) - 1
2068 return left, top, right, bottom
2069end
2070
2071Objects.Text.attributeChanged = function(self, key, oldValue, newValue)
2072 if (key == "text") then
2073 self.width = #newValue
2074 end
2075end
2076
2077-- >> Variable
2078
2079Objects.Variable = {}
2080Objects.Variable.attributes = {
2081 { description = "Object ID", attrName = "objID", attrType = "text" };
2082 { description = "Load Function", attrName = "load", attrType = "text" };
2083}
2084
2085Objects.Variable.new = function(self)
2086 self.width = 1
2087 self.height = 1
2088end
2089
2090Objects.Variable.get = function(self, value)
2091 local buffer = Buffer:new()
2092
2093 if editMode and showCustomWindow ~= "Editor" then
2094 buffer:init(1, 1, self.path, ObjectColors.Editor.marker)
2095 elseif value then
2096 buffer:init(string.len(value), 1, self.path)
2097 buffer:addText(1, 1, value)
2098 else
2099 buffer:init(1, 1, self.path)
2100 end
2101
2102 return buffer
2103end
2104
2105-- >> ProgressBar
2106
2107Objects.ProgressBar = {}
2108Objects.ProgressBar.attributes = {
2109 { description = "Object ID", attrName = "objID", attrType = "text" };
2110 { description = "Load Function", attrName = "load", attrType = "text" };
2111}
2112
2113Objects.ProgressBar.new = function(self, maxWidth)
2114 self.length = (maxWidth < Size.ProgressBar.length) and maxWidth or Size.ProgressBar.length
2115 self.lengthPercent = maxX / self.length
2116 self.width = self.length
2117 self.height = 1
2118 self.direction = "right"
2119 self.objID = "newProgressBar"
2120 self.canScale = true
2121end
2122
2123Objects.ProgressBar.get = function(self, value)
2124 local length = self.length
2125 local direction = (isValidDirection(self.direction)) and self.direction or "right"
2126 local orientation = getOrientation(direction) or error("Direction " .. direction .. " is invalid!")
2127 value = value or ProgressBarValues[self.objID]
2128
2129 if (orientation == "horizontal") then
2130 width, height = length, 1
2131 else
2132 width, height = 1, length
2133 end
2134
2135 local buffer = Buffer:new()
2136 buffer:init(width, height, self.path, ObjectColors.ProgressBar.background)
2137
2138 if value then
2139 local color
2140 if (value < 33) then
2141 color = ObjectColors.ProgressBar.low
2142 elseif (value > 66) then
2143 color = ObjectColors.ProgressBar.high
2144 else
2145 color = ObjectColors.ProgressBar.medium
2146 end
2147
2148 local filled = math.floor((length / 100) * value)
2149 local valueBuffer = Objects.Line.get(getOrientation(direction), filled, color)
2150 local addX, addY = 1, 1
2151
2152 if (direction == "left") then
2153 addX = width - filled + 1
2154 elseif (direction == "up") then
2155 addY = height - filled + 1
2156 end
2157
2158 buffer:addBuffer(addX, addY, valueBuffer)
2159 end
2160
2161 return buffer
2162end
2163
2164Objects.ProgressBar.getMovePos = function(self)
2165 local dir = self.direction
2166 local length = self.length
2167 local x, y = Objects.getAbsolutePos(self)
2168
2169 if (dir == "left") then
2170 return x + length - 1, y
2171 elseif (dir == "up") then
2172 return x, y + length - 1
2173 else
2174 return x, y
2175 end
2176end
2177
2178Objects.ProgressBar.getScalePos = function(self)
2179 local dir = self.direction
2180 local length = self.length
2181 local x, y = Objects.getAbsolutePos(self)
2182
2183 if (dir == "right") then
2184 return x + length - 1, y
2185 elseif (dir == "down") then
2186 return x, y + length - 1
2187 else
2188 return x, y
2189 end
2190end
2191
2192Objects.ProgressBar.scale = function(self, x, y)
2193 local moveX, moveY = Objects.getMovePos(self)
2194 local relMoveX, relMoveY = Path.getRelativePos(self.path, moveX, moveY)
2195 local length
2196 local direction
2197 local newX, newY
2198 local width, height
2199
2200 if (x < relMoveX and y == relMoveY) then -- Clicked left of the progressBar.
2201 length = relMoveX - x + 1
2202 direction = "left"
2203 newX, newY = x, y
2204 width, height = length, 1
2205 elseif (x == relMoveX and y < relMoveY) then -- Clicked above the progressBar.
2206 length = relMoveY - y + 1
2207 direction = "up"
2208 newX, newY = x, y
2209 width, height = 1, length
2210 elseif (x > relMoveX and y == relMoveY) then -- Clicked right of the progressBar.
2211 length = x - relMoveX + 1
2212 direction = "right"
2213 newX, newY = relMoveX, relMoveY
2214 width, height = length, 1
2215 elseif (x == relMoveX and y > relMoveY) then -- Clicked below the progressBar.
2216 length = y - relMoveY + 1
2217 direction = "down"
2218 newX, newY = relMoveX, relMoveY
2219 width, height = 1, length
2220 else
2221 return
2222 end
2223
2224 if (length > 2) then
2225 local xDiff, yDiff = newX - self.x, newY - self.y
2226
2227 self.absoluteX, self.absoluteY = self.absoluteX + xDiff, self.absoluteY + yDiff
2228 self.x, self.y = newX, newY
2229 self.direction = direction
2230 self.width, self.height = width, height
2231 self.length = length
2232 self.lengthPercent = length / maxX
2233 end
2234end
2235
2236-- >> Input
2237
2238Objects.Input = {}
2239Objects.Input.attributes = {
2240{ description = "Object ID", attrName = "objID", attrType = "text" };
2241 { description = "Message", attrName = "message", attrType = "text" };
2242 { description = "Is password", attrName = "isPassword", attrType = "bool" };
2243}
2244
2245Objects.Input.new = function(self)
2246 self.message = "Enter something."
2247 self.isPassword = false
2248 self.width = 2
2249 self.height = 1
2250 self.canClick = true
2251end
2252
2253Objects.Input.get = function(self)
2254 local userInput = ObjectData.Input[self.objID] or ""
2255 local width, height = 2 + string.len(userInput), 1
2256
2257 local buffer = Buffer:new()
2258 buffer:init(width, height, self.path, ObjectColors.Input.default)
2259 if userInput ~= "" then
2260 buffer:addText(2, 1, userInput)
2261 end
2262
2263 return buffer
2264end
2265
2266-- Returns: "text_changed" event, objID, text
2267Objects.Input.click = function(self)
2268 local x = self.absoluteX
2269 local y = self.absoluteY
2270 local objID = self.objID
2271 local message = self.message
2272 local isPassword = (self.isPassword == nil) and false or self.isPassword
2273 local maxLength = self.maxLength
2274 local existingInput = ObjectData.Input[objID]
2275
2276 out.setBackgroundColor(ObjectColors.background)
2277 out.setCursorPos(x, y)
2278 if (existingInput ~= nil) then -- Clear the text on the input object.
2279 out.write(string.rep(" ", string.len(existingInput) + 2))
2280 else
2281 out.write(" ")
2282 end
2283 ObjectData.Input[objID] = nil
2284
2285 out.setCursorPos(x, y)
2286 if not outIsTerm then
2287 -- make the input-object yellow
2288 out.setBackgroundColor(ObjectColors["Input"].active)
2289 out.write(" ")
2290 out.setBackgroundColor(ObjectColors.background)
2291 end
2292
2293 if outIsTerm then
2294 out.setCursorPos(x + 1, y)
2295 end
2296
2297 local userInput = readUserInput(message, isPassword)
2298 if (userInput ~= nil) then
2299 ObjectData.Input[objID] = userInput
2300 end
2301
2302 out.setCursorPos(x, y)
2303 out.setBackgroundColor(ObjectColors.Input.default)
2304 out.setTextColor(ObjectColors.Input.text)
2305
2306 out.write(" ")
2307 if (userInput ~= nil and userInput ~= "") then
2308 if isPassword then
2309 for i = 1, string.len(userInput) do
2310 out.write("*")
2311 end
2312 else
2313 out.write(userInput)
2314 end
2315 end
2316
2317 out.write(" ")
2318 out.setBackgroundColor(ObjectColors.background)
2319 out.setTextColor(ObjectColors.text)
2320
2321 return "text_changed", {self.objID, userInput}
2322end
2323
2324-- >> List
2325
2326Objects.List = {}
2327Objects.List.attributes = {
2328 { description = "Object ID", attrName = "objID", attrType = "text" };
2329 { description = "Items", attrName = "Items", attrType = "table" };
2330 { description = "Is multiselect", attrName = "isMultiselect", attrType = "bool" };
2331}
2332
2333Objects.List.new = function(self)
2334 self.Items = {}
2335 self.objID = "testList"
2336 self.isMultiselect = false
2337 self.canClick = true
2338end
2339
2340Objects.List.get = function(self)
2341 assert(self)
2342
2343 local buffer = Buffer:new()
2344
2345 if self.load then
2346 self.Items = self.load()
2347 end
2348
2349 self.Items = self.Items or { "empty" }
2350
2351 if (#self.Items == 0) then
2352 self.Items = { "empty" }
2353 end
2354
2355 self.width = getLongestString(self.Items) + 2
2356 self.height = #self.Items
2357
2358 if not ObjectData.List[self.objID] then
2359 ObjectData.List[self.objID] = {}
2360 end
2361
2362 buffer:init(self.width, self.height, self.path, ObjectColors.List.default)
2363
2364 local line = 1
2365 for key, element in pairs(self.Items) do
2366 if (ObjectData.List[self.objID][key]) then
2367 buffer:addBuffer(1, line, Objects.Line.get("horizontal", self.width, ObjectColors.List.active))
2368 end
2369 buffer:addText(2, line, self.Items[line])
2370 line = line + 1
2371 end
2372
2373 return buffer
2374end
2375
2376-- Returns: "selection_changed" event, objID, key, true or false
2377Objects.List.click = function(self, x, y)
2378 local objID = self.objID
2379 local isMultiselect = self.isMultiselect
2380 local itemSelected = ObjectData.List[objID][y]
2381
2382 if (isMultiselect) then
2383 ObjectData.List[objID][y] = not itemSelected
2384 else
2385 ObjectData.List[objID] = {}
2386 ObjectData.List[objID][y] = true
2387 end
2388
2389 Objects.draw(self)
2390
2391 return "selection_changed", {self.objID, y, ObjectData.List[objID][y]}
2392end
2393
2394-- Same as List.click
2395Objects.List.drag = function(self, x, y)
2396 return Objects.List.click(self, x, y)
2397end
2398
2399Objects.List.getFirstSelectedKey = function(self)
2400 assert(self)
2401
2402 local objID = self.objID
2403
2404 for key, value in pairs(ObjectData.List[objID]) do
2405 if (value == true and self.Items[key] ~= "empty") then
2406 return key
2407 end
2408 end
2409
2410 return nil
2411end
2412
2413Objects.List.getFirstSelectedValue = function(self)
2414 assert(self)
2415
2416 key = Objects.List.getFirstSelectedKey(self)
2417
2418 if key then
2419 return self.Items[key]
2420 else
2421 return nil
2422 end
2423end
2424
2425-- >> FileSelector
2426
2427Objects.FileSelector = {}
2428Objects.FileSelector.attributes = {
2429 { description = "Object ID", attrName = "objID", attrType = "text" };
2430}
2431
2432Objects.FileSelector.new = function(self)
2433 self.width = string.len(Text.fileSelector)
2434 self.height = 1
2435 self.isMultiselect = false
2436 self.canClick = true
2437end
2438
2439Objects.FileSelector.get = function(self)
2440error("not yet implemented")
2441 local objectID = objectID
2442 local x = self.x
2443 local y = self.y
2444 local isMultiselect = self.isMultiselect
2445
2446 out.setBackgroundColor(ObjectColors["FileSelector"].default)
2447 out.setTextColor(ObjectColors["FileSelector"].text)
2448
2449 out.setCursorPos(x, y)
2450 out.write(Text.fileSelector)
2451
2452 if (ObjectData.FileSelector[objectID] ~= nil) then
2453 out.setBackgroundColor(ObjectColors.background)
2454 out.setTextColor(ObjectColors.text)
2455 local files = ObjectData.FileSelector[objectID]
2456 out.write(" ")
2457 if (type(files) == "table") then
2458 local sep = ""
2459 for _, fileName in pairs(files) do
2460 term.write(sep .. fileName)
2461 sep = ", "
2462 end
2463 else
2464 out.write(files)
2465 end
2466 end
2467
2468 out.setBackgroundColor(ObjectColors.background)
2469 out.setTextColor(ObjectColors.text)
2470end
2471
2472Objects.FileSelector.click = function(self, x, y)
2473error("Not yet implemented")
2474-- TODO
2475 local finished = false
2476 local path = "/"
2477 local list = {}
2478
2479 while not finished do
2480 clearScreen()
2481 out.setCursorPos(2, 1)
2482 out.write("Path: " .. path)
2483
2484 list = getFileList(path)
2485
2486 out.setTextColor(ObjectColors.FileSelector.text)
2487 end
2488end
2489
2490-- >> CheckBox
2491Objects.CheckBox = {}
2492Objects.CheckBox.attributes = {
2493 { description = "Object ID", attrName = "objID", attrType = "text" };
2494 { description = "Checked by default", attrName = "defaultIsChecked", attrType = "bool" };
2495}
2496
2497Objects.CheckBox.new = function(self)
2498 self.objID = "newCheckBox"
2499 self.canClick = true
2500 self.canScale = false
2501 self.defaultIsChecked = false
2502 self.text = "new CheckBox"
2503 self.width = #self.text + 2
2504 self.height = 1
2505end
2506
2507Objects.CheckBox.init = function(self)
2508 if (self.defaultIsChecked == true) then
2509 ObjectData.CheckBox[self.objID] = true
2510 end
2511end
2512
2513Objects.CheckBox.get = function(self)
2514 if (ObjectData.CheckBox[self.objID] == nil) then
2515 ObjectData.CheckBox[self.objID] = self.defaultIsChecked
2516 end
2517
2518 local buffer = Buffer:new()
2519 local isChecked = ObjectData.CheckBox[self.objID]
2520 local char = isChecked and "X" or " "
2521
2522 buffer:init(self.width, self.height, self.path)
2523 buffer:setPixel(1, 1,
2524 { background = ObjectColors.CheckBox.default,
2525 text = ObjectColors.CheckBox.active,
2526 char = char })
2527 buffer:addText(3, 1, self.text)
2528
2529 return buffer
2530end
2531
2532-- returns "checked" event, object ID, checked state
2533Objects.CheckBox.click = function(self)
2534 ObjectData.CheckBox[self.objID] = not ObjectData.CheckBox[self.objID]
2535 Objects.draw(self)
2536
2537 return "checked", { self.objID, ObjectData.CheckBox[self.objID] }
2538end
2539
2540Objects.CheckBox.attributeChanged = function(self, key, oldValue, newValue)
2541 if (key == "text") then
2542 self.width = #newValue + 2
2543 end
2544end
2545
2546-- >> RadioButton
2547
2548Objects.RadioButton = {}
2549Objects.RadioButton.attributes = {
2550 { description = "Object ID", attrName = "objID", attrType = "text" };
2551 { description = "Text", attrName = "text", attrType = "text" };
2552 { description = "Group", attrName = "group", attrType = "text" };
2553 { description = "Checked by default", attrName = "defaultIsChecked", attrType = "bool" }
2554}
2555
2556Objects.RadioButton.new = function(self)
2557 self.objID = "newRadioButton1"
2558 self.defaultIsChecked = false
2559 self.group = "default"
2560 self.text = "new RadioButton"
2561 self.width = #self.text + 2
2562 self.height = 1
2563 self.canClick = true
2564 self.canScale = false
2565end
2566
2567Objects.RadioButton.init = function(self)
2568 if (self.defaultIsChecked == true) then
2569 ObjectData.RadioButton[self.objID] = true
2570 end
2571end
2572
2573Objects.RadioButton.get = function(self)
2574 local buffer = Buffer:new()
2575 local color
2576
2577 if ObjectData.RadioButton[self.objID] and not editMode then
2578 color = ObjectColors.RadioButton.active
2579 else
2580 color = ObjectColors.RadioButton.default
2581 end
2582
2583 buffer:init(#self.text + 2, 1, self.path)
2584 buffer:setPixel(1, 1, { background = color, char = " "})
2585 buffer:addText(3, 1, self.text)
2586
2587 return buffer
2588end
2589
2590Objects.RadioButton.update = function(container, groupName, clickedID)
2591 for _, object in pairs(container.Children) do
2592 if (type(object) == "table") then
2593 if (object.objType == "RadioButton" and object.group == groupName) then
2594 ObjectData.RadioButton[object.objID] = (clickedID == object.objID)
2595 Objects.draw(object)
2596 elseif (object.isContainer) then
2597 Objects.RadioButton.update(object, groupName, clickedID)
2598 end
2599 end
2600 end
2601end
2602
2603-- returns "radio_changed" event, object ID
2604Objects.RadioButton.click = function(self)
2605 Objects.RadioButton.update(getCurrentWindow(), self.group, self.objID)
2606
2607 return "selection_changed", { self.objID }
2608end
2609
2610Objects.RadioButton.attributeChanged = function(self, key, oldValue, newValue)
2611 if (key == "text") then
2612 self.with = #newValue + 2
2613 end
2614end
2615
2616-- >> Slider
2617
2618Objects.Slider = {}
2619Objects.Slider.attributes = {
2620 { description = "Object ID", attrName = "objID", attrType = "text" };
2621 { description = "Minimum value", attrName = "minValue", attrType = "number" };
2622 { description = "Maximum value (affects length)", attrName = "maxValue", attrType = "number" };
2623}
2624
2625Objects.Slider.new = function(self)
2626 self.objID = "newSlider"
2627 self.width = Size.Slider.length
2628 self.height = 1
2629 self.minValue = 0
2630 self.maxValue = Size.Slider.length - 1
2631 self.canClick = true
2632 self.canScale = false
2633end
2634
2635Objects.Slider.init = function(self)
2636 ObjectData.Slider[self.objID] = self.minValue
2637end
2638
2639Objects.Slider.get = function(self)
2640 local buffer = Buffer:new()
2641
2642 buffer:init(self.width, self.height, self.path, ObjectColors.Slider.default, ObjectColors.Slider.text)
2643 buffer:addText(1, 1, "|" .. string.rep("-", self.width - 2) .. "|")
2644
2645 if ObjectData.Slider[self.objID] and not editMode then
2646 local pixel = { background = ObjectColors.Slider.active, text = ObjectColors.Slider.activeText, char = "|" }
2647 buffer:setPixel(ObjectData.Slider[self.objID] - self.minValue + 1, 1, pixel)
2648 end
2649
2650 return buffer
2651end
2652
2653Objects.Slider.attributeChanged = function(self, key, oldValue, newValue)
2654 if ((key == "minValue" or key == "manValue") and newValue % 1 ~= 0) then
2655 self[key] = round(newValue)
2656 end
2657
2658 if (key == "minValue") then
2659 if (newValue >= self.maxValue) then
2660 self[key] = oldValue
2661 else -- adapt slider width
2662 self.width = self.maxValue - self.minValue + 1
2663 end
2664 elseif (key == "maxValue") then
2665 if (newValue <= self.minValue) then
2666 self[key] = oldValue
2667 else -- adapt slider width
2668 self.width = self.maxValue - self.minValue + 1
2669 end
2670 end
2671end
2672
2673-- returns "slider_changed" event, object ID, value
2674Objects.Slider.click = function(self, x, y)
2675 ObjectData.Slider[self.objID] = x + self.minValue - 1
2676 Objects.draw(self)
2677
2678 return "slider_changed", { self.objID, ObjectData.Slider[self.objID] }
2679end
2680
2681Objects.Slider.drag = function(self, x, y)
2682 return Objects.Slider.click(self, x, y)
2683end
2684
2685-- >> DropDownList
2686
2687Objects.DropDownList = {}
2688Objects.DropDownList.attributes = {
2689 { description = "Object ID", attrName = "objID", attrType = "text" };
2690 { description = "Items", attrName = "Items", attrType = "table" };
2691 { description = "Load Function", attrName = "load", attrType = "text" };
2692}
2693
2694Objects.DropDownList.new = function(self, maxWidth)
2695 self.Items = { "empty" }
2696 self.objID = "newDropDownList"
2697 self.width = (Size.DropDownList.width > maxWidth) and maxWidth or Size.DropDownList.width
2698 self.height = 1
2699 self.canClick = true
2700 self.canScale = true
2701end
2702
2703Objects.DropDownList.get = function(self)
2704 assert(self)
2705
2706 if self.load then
2707 self.Items = self.load()
2708 elseif not self.Items then
2709 self.Items = { "empty" }
2710 end
2711
2712 local buffer = Buffer:new()
2713 buffer:init(self.width, 1, self.path, ObjectColors.DropDownList.default, ObjectColors.DropDownList.text)
2714
2715 if ObjectData.DropDownList[self.objID] then
2716 buffer:addText(1, 1, self.Items[ObjectData.DropDownList[self.objID]])
2717 elseif #self.Items > 0 then
2718 ObjectData.DropDownList[self.objID] = 1
2719 buffer:addText(1, 1, self.Items[ObjectData.DropDownList[self.objID]])
2720 end
2721
2722 buffer:addText(self.width - 2, 1, " V")
2723
2724 return buffer
2725end
2726
2727-- Returns: "selection_changed" event, objID, key
2728Objects.DropDownList.click = function(self, x, y)
2729 assert(self)
2730
2731 local selectedItem = ObjectData.DropDownList[self.objID]
2732 local drawY
2733 local height = #self.Items
2734
2735 -- Determine whether the list should be displayed below or above the object.
2736
2737 if height >= maxX then -- Not enough space vertically.
2738 drawY = 1
2739 elseif maxX - height > self.absoluteY then
2740 drawY = self.absoluteY + 1
2741 elseif self.absoluteY - height > 0 then
2742 drawY = self.absoluteY - height
2743 else
2744 drawY = maxY - height
2745 end
2746
2747 out.setTextColor(ObjectColors.DropDownList.text)
2748
2749 for index = 1, height do
2750 if (index == ObjectData.DropDownList[self.objID]) then
2751 out.setBackgroundColor(ObjectColors.DropDownList.active)
2752 else
2753 out.setBackgroundColor(ObjectColors.DropDownList.default)
2754 end
2755
2756 out.setCursorPos(self.absoluteX, drawY + index - 1)
2757 out.write(self.Items[index] .. string.rep(" ", self.width - #self.Items[index] - 1))
2758 end
2759
2760 out.setBackgroundColor(ObjectColors.background)
2761 out.setTextColor(ObjectColors.text)
2762
2763 local x, y, mouseButton = getCursorInput()
2764
2765 if (x >= self.absoluteX and
2766 x <= self.absoluteX + self.width - 1 and
2767 y >= self.absoluteY + 1 and
2768 y <= self.absoluteY + height) then
2769 local selectedIndex = y - self.absoluteY
2770
2771 ObjectData.DropDownList[self.objID] = selectedIndex
2772 end
2773
2774 drawWindow()
2775
2776 return "selection_changed", { self.objID, ObjectData.DropDownList[self.objID] }
2777end
2778
2779Objects.DropDownList.scale = function(self, x, y)
2780 assert(self)
2781 assert(x)
2782 assert(y)
2783
2784 local length = x - self.x + 1
2785
2786 if (length >= 3) then
2787 self.width = length
2788 end
2789end
2790
2791-- >>> Containers
2792
2793Objects.Container = {}
2794
2795Objects.Container.getNextFreeKey = function(self)
2796 local nextKey = 1
2797
2798 while self.Children[nextKey] ~= nil do
2799 nextKey = nextKey + 1
2800 end
2801
2802 return nextKey
2803end
2804
2805-- Returns the area of the container which stores
2806-- its children.
2807Objects.Container.getContentArea = function(self)
2808 assert(self)
2809 local objType = self.objType
2810
2811 if (Objects.Container[objType].getContentArea) then
2812 return Objects.Container[objType].getContentArea(self)
2813 else
2814 local left = self.x + 1
2815 local top = self.y + 1
2816 local right = self.x + self.width - 2
2817 local bottom = self.y + self.height - 2
2818 return left, top, right, bottom
2819 end
2820end
2821
2822-- Determines whether the container itself or its
2823-- content area is at the given position.
2824Objects.Container.contentAreaClicked = function(self, x, y)
2825 local left, top, right, bottom = Objects.Container.getContentArea(self)
2826
2827 return (x >= left and x <= right and y >= top and y <= bottom)
2828end
2829
2830-- Draws the buffer and trims it before that if
2831-- necessary.
2832Objects.Container.drawBuffer = function(self, buffer, path, x, y, absoluteX, absoluteY, nestLevel)
2833 --log("Objects.Container.drawBuffer", "FUNC")
2834 local modX, modY = Objects.Container.getPosModifier(self, x, y)
2835 x, y = x + modX, y + modY
2836
2837 if (self.objType ~= "Window") then
2838 local containerLeft, containerTop, containerRight, containerBottom = 1, 1, self.width - 1, self.height - 1
2839 local objLeft, objTop, objRight, objBottom = x, y, x + buffer.width - 1, y + buffer.height - 1
2840
2841 --log("Container dimensions: left: " .. containerLeft .. ", top: " .. containerTop .. ", right: " .. containerRight .. ", bottom: " ..containerBottom .. ".", "DEBUG")
2842 --log("Object dimensions: left: " .. objLeft .. ", top: " .. objTop .. ", right: " .. objRight .. ", bottom: " ..objBottom .. ".", "DEBUG")
2843
2844 if (objLeft > self.width or
2845 objTop > self.height or
2846 objRight < 0 or
2847 objBottom < 0) then
2848 return
2849 elseif (objLeft < containerLeft or objTop < containerTop or objRight >= containerRight or objBottom >= containerBottom) then
2850 -- Object goes over the border. Trim it.
2851 local trimLeft = (containerLeft - objLeft) > 0 and containerLeft - objLeft or 0
2852 local trimTop = (containerTop - objTop) > 0 and containerTop - objTop or 0
2853 local trimRight = (objRight - containerRight + 1) > 0 and objRight - containerRight + 1 or 0
2854 local trimBottom = (objBottom - containerBottom + 1) > 0 and objBottom - containerBottom + 1 or 0
2855 buffer = buffer:trim(trimLeft, trimTop, trimRight, trimBottom)
2856
2857 if (trimLeft > 0) then
2858 x = x + trimLeft
2859 absoluteX = absoluteX + trimLeft
2860 end
2861 if (trimTop > 0) then
2862 y = y + trimTop
2863 absoluteY = absoluteY + trimTop
2864 end
2865 end
2866 end
2867
2868 if (nestLevel < #path - 1) then
2869 local container = Path.getContainerAt(path, nestLevel + 1)
2870 local relX, relY = Objects.Container.getRelativePos(container, x, y)
2871 Objects.Container.drawBuffer(container, buffer, path, relX, relY, absoluteX, absoluteY, nestLevel + 1)
2872 else
2873 buffer:draw(absoluteX + modX, absoluteY + modY)
2874 end
2875end
2876
2877Objects.Container.getRelativePos = function(self, x, y)
2878 local left, top, right, bottom = Objects.Container.getContentArea(self)
2879
2880 --log("Objects.Container.getRelativePos", "FUNC")
2881 --log(self.objType .. " ID: " .. self.objID .. ", x: " .. x .. ", y: " .. y .. ".", "ATTR")
2882 --log("Left: " .. left .. ", top: " .. top .. ", right: " .. right .. ", bottom: " .. bottom .. ".", "DEBUG")
2883
2884 retX = x - left + 1
2885 retY = y - top + 1
2886
2887 return retX, retY
2888end
2889
2890Objects.Container.getParentsRelativePos = function(self, x, y)
2891 local left, top, right, bottom = Objects.Container.getContentArea(self)
2892
2893 return x + left - 1, y + top - 1
2894end
2895
2896Objects.Container.new = function(self, maxWidth, maxHeight)
2897 --log("Objects.Container.new", "FUNC")
2898 local defaultWidth, defaultHeight = Size.Container.width, Size.Container.height
2899
2900 self.isContainer = true
2901 self.canScale = true
2902 self.Children = {}
2903 self.width = (maxWidth < defaultWidth) and maxWidth or defaultWidth
2904 self.height = (maxHeight < defaultHeight) and maxHeight or defaultHeight
2905
2906 --log("New container dimensioins: " .. self.width .. " x " .. self.height .. ".")
2907
2908 if (Objects.Container[self.objType].new) then
2909 Objects.Container[self.objType].new(self)
2910 end
2911end
2912
2913Objects.Container.init = function(self)
2914 assert(self)
2915
2916 local objType = self.objType
2917
2918 if (Objects.Container[objType] and Objects.Container[objType].init) then
2919 Objects.Container[objType].init()
2920 end
2921
2922 for _, object in pairs(self.Children) do
2923 if (type(object) == "table") then
2924 local objType = object.objType
2925
2926 if (Objects[objType] and Objects[objType].init) then
2927 log("Initializting " .. objType .. " " .. object.objID .. ".", "INFO")
2928 Objects[objType].init(object)
2929 elseif (object.isContainer) then
2930 Objects.Container.init(object)
2931 end
2932 end
2933 end
2934end
2935
2936Objects.Container.get = function(self)
2937 assert(self)
2938
2939 local left, top, right, bottom = Objects.Container.getContentArea(self)
2940 local minWidth, minHeight = right - left + 1, bottom - top + 1
2941 local width, height = getNecessaryBufferSize(self.Children, minWidth, minHeight)
2942
2943 local buffer = Buffer:new()
2944 buffer:init(width, height, {})
2945
2946 for objectID, object in pairs(self.Children) do
2947 local objectBuffer = Objects.get(object)
2948 buffer:addBuffer(object.x, object.y, objectBuffer)
2949 end
2950
2951 return Objects.Container[self.objType].get(self, buffer)
2952end
2953
2954Objects.Container.getPosModifier = function(self, x, y)
2955 assert(self)
2956
2957 --log("Objects.Container.getPosModifier", "FUNC")
2958 --log("Object type: " .. tostring(self.objType) .. ", ID: " .. tostring(self.objID) .. ".", "INFO")
2959 --log("Position: " .. x .. ", " .. y .. ".", "INFO")
2960
2961 local objType = self.objType
2962 if (Objects.Container[objType] and Objects.Container[objType].getPosModifier) then
2963 local newX, newY = Objects.Container[objType].getPosModifier(self, x, y)
2964 --log("Modifier: " .. newX .. ", " .. newY .. ".", "INFO")
2965 return newX, newY
2966 else
2967 --log("Position not modified.")
2968 return 0, 0
2969 end
2970end
2971
2972Objects.Container.move = function(self, addX, addY)
2973 self.absoluteX = self.absoluteX + addX
2974 self.absoluteY = self.absoluteY + addY
2975
2976 for _, child in pairs(self.Children) do
2977 if child.isContainer then
2978 Objects.Container.move(child, addX, addY)
2979 else
2980 child.absoluteX = child.absoluteX + addX
2981 child.absoluteY = child.absoluteY + addY
2982 end
2983 end
2984end
2985
2986-- >> Window
2987
2988Objects.Container.Window = {}
2989Objects.Container.Window.new = function(windowName)
2990 local object = {}
2991 object.objType = "Window"
2992 object.Children = {}
2993 object.width, object.height = maxX, maxY
2994
2995 return object
2996end
2997
2998Objects.Container.Window.create = function(windowName)
2999 assert(windowName)
3000
3001 local windowContainer = getWindowContainer()
3002 local object = Objects.Container.Window.new(windowName)
3003
3004 windowContainer.Children[windowName] = object
3005
3006 log("Created window \"" .. windowName .. "\" for window container \"" .. tostring(showCustomWindow) .. "\".", "INFO")
3007end
3008
3009Objects.Container.Window.get = function(self, contentBuffer)
3010 return contentBuffer
3011end
3012
3013Objects.Container.Window.getContentArea = function(self)
3014 return 1, 1, maxX, maxY
3015end
3016
3017-- >> Panel
3018
3019Objects.Container.Panel = {}
3020Objects.Container.Panel.attributes = {}
3021
3022Objects.Container.Panel.get = function(self, contentBuffer)
3023 --log("Objects.Container.Panel.get", "FUNC")
3024 local buffer = Buffer:new()
3025 buffer:init(self.width, self.height, self.path, ObjectColors.Container.Panel.border)
3026 buffer:addBuffer(2, 2, contentBuffer)
3027 buffer:addBuffer(self.width, 1, Objects.Line.get("vertical", self.height, ObjectColors.Container.Panel.border, self.path))
3028 buffer:addBuffer(1, self.height, Objects.Line.get("horizontal", self.width, ObjectColors.Container.Panel.border, self.path))
3029
3030 return buffer
3031end
3032
3033-- >> ScrollView
3034
3035Objects.Container.ScrollView = {}
3036Objects.Container.ScrollView.attributes = {}
3037
3038Objects.Container.ScrollView.new = function(self)
3039 self.scrollX = 0
3040 self.scrollY = 0
3041 self.maxScrollX = 0
3042 self.maxScrollY = 0
3043 self.scrollXEnabled = false
3044 self.scrollYEnabled = true
3045end
3046
3047Objects.Container.ScrollView.get = function(self, contentBuffer)
3048 --log("Objects.Container.ScrollView.get", "FUNC")
3049 --log("ScrollView (ID: " .. tostring(self.objID) .. ")", "INFO")
3050
3051 local buffer = Buffer:new()
3052 buffer:init(self.width, self.height, self.path, ObjectColors.Container.ScrollView.border)
3053 buffer:addBuffer(2 - self.scrollX, 2 - self.scrollY, contentBuffer)
3054 buffer:makeBorder(self.path, ObjectColors.Container.ScrollView.border)
3055
3056 --local edgePixel = { background=ObjectColors.Container.ScrollView.border, path=self.path }
3057 --buffer:setPixel(self.width, 1, edgePixel)
3058 --buffer:setPixel(self.width, self.height, edgePixel)
3059 --buffer:setPixel(1, self.height, edgePixel)
3060
3061 if (self.scrollXEnabled) then
3062 self.maxScrollX = contentBuffer.width - self.width + 1
3063
3064 buffer:addBuffer(2, self.height, Objects.Line.get("horizontal", self.width - 2, ObjectColors.Container.ScrollView.scrollBackground))
3065
3066 if (self.width > 4) then
3067 local scrollBarInfo = getScrollBarInfo(self.scrollX, self.width, contentBuffer.width)
3068 buffer:addBuffer(scrollBarInfo.pos + 3, self.height, Objects.Line.get("horizontal", scrollBarInfo.size, ObjectColors.Container.ScrollView.scrollForeground, self.path))
3069 end
3070
3071 buffer:setPixel(2, self.height, { char="<", path=self.path })
3072 buffer:setPixel(self.width - 1, self.height, { char=">", path=self.path })
3073 else
3074 --buffer:addBuffer(2, self.height, Objects.Line.get("horizontal", self.width - 2, ObjectColors.Container.ScrollView.border, self.path))
3075 end
3076
3077 if (self.scrollYEnabled) then
3078 self.maxScrollY = contentBuffer.height - self.height + 1
3079 buffer:addBuffer(self.width, 2, Objects.Line.get("vertical", self.height - 2, ObjectColors.Container.ScrollView.scrollBackground))
3080
3081 if (self.height > 4) then
3082 local scrollBarInfo = getScrollBarInfo(self.scrollY, self.height, contentBuffer.height)
3083 buffer:addBuffer(self.width, scrollBarInfo.pos + 3, Objects.Line.get("vertical", scrollBarInfo.size, ObjectColors.Container.ScrollView.scrollForeground, self.path))
3084 end
3085
3086 buffer:setPixel(self.width, 2, { char="^", path=self.path })
3087 buffer:setPixel(self.width, self.height - 1, { char="V", path=self.path })
3088 else
3089 --buffer:addBuffer(self.width, 2, Objects.Line.get("vertical", self.height - 2, ObjectColors.Container.ScrollView.border, self.path))
3090 end
3091
3092 return buffer
3093end
3094
3095Objects.Container.ScrollView.click = function(self, x, y)
3096 if (x == self.width and y == 2) then -- Up
3097 --log("Up")
3098 if (self.scrollY > 0) then
3099 self.scrollY = self.scrollY - 1
3100 end
3101 elseif (x == self.width and y == self.height - 1) then -- Down
3102 --log("Down")
3103 if (self.scrollY < self.maxScrollY) then
3104 self.scrollY = self.scrollY + 1
3105 end
3106 elseif (x == 2 and y == self.height) then -- Left
3107 --log("Left")
3108 if (self.scrollX > 0) then
3109 self.scrollX = self.scrollX - 1
3110 end
3111 elseif (x == self.width - 1 and y == self.height) then -- Right
3112 --log("Right")
3113 if (self.scrollX < self.maxScrollX) then
3114 self.scrollX = self.scrollX + 1
3115 end
3116 end
3117
3118 WindowBuffer:addBuffer(self.absoluteX, self.absoluteY, Objects.get(self))
3119 Objects.draw(self)
3120end
3121
3122Objects.Container.ScrollView.getPosModifier = function(self, x, y)
3123 return self.scrollX * -1, self.scrollY * -1
3124end
3125
3126Objects.Container.ScrollView.addMarker = function(self, buffer)
3127
3128 if not (self.scrollXEnabled and self.scrollYEnabled) then
3129 if (not self.scrollXEnabled and self.width > 2) then
3130 buffer:addBuffer(2, self.height, Objects.Line.get("horizontal", self.width - 2, ObjectColors.Editor.editMarker))
3131 end
3132
3133 if (not self.scrollYEnabled and self.height > 2) then
3134 buffer:addBuffer(self.width, 2, Objects.Line.get("vertical", self.height - 2, ObjectColors.Editor.editMarker))
3135 end
3136 end
3137end
3138
3139Objects.Container.ScrollView.editorClick = function(self, x, y)
3140 -- Check whether an arrow has been clicked.
3141 if (self.scrollYEnabled and x == self.width and y == 2) then -- Up
3142 --log("Up")
3143 if (self.scrollY > 0) then
3144 self.scrollY = self.scrollY - 1
3145 end
3146
3147 return true
3148 elseif (self.scrollYEnabled and x == self.width and y == self.height - 1) then -- Down
3149 --log("Down")
3150 self.scrollY = self.scrollY + 1
3151 return true
3152 elseif (self.scrollXEnabled and x == 2 and y == self.height) then -- Left
3153 --log("Left")
3154 if (self.scrollX > 0) then
3155 self.scrollX = self.scrollX - 1
3156 end
3157
3158 return true
3159 elseif (self.scrollXEnabled and x == self.width - 1 and y == self.height) then -- Right
3160 --log("Right")
3161 self.scrollX = self.scrollX + 1
3162 return true
3163 else -- Check whether the scrollBar has been clicked.
3164 if (x >= 1 and x <= self.width - 1 and y == self.height) then
3165 self.scrollXEnabled = not self.scrollXEnabled
3166 return true
3167 elseif (x == self.width and y > 1 and y < self.height - 1) then
3168 self.scrollYEnabled = not self.scrollYEnabled
3169 return true
3170 end
3171 end
3172
3173 return false
3174end
3175
3176-- >> Non-addable objects.
3177
3178-- >> Line
3179
3180Objects.Line = {}
3181Objects.Line.get = function(orientation, length, color, path)
3182 if (orientation ~= "horizontal" and orientation ~= "vertical") then
3183 orientation = getOrientation(orientation) or error("Orientation " .. orientation .. " is invalid!", 1)
3184 end
3185
3186 assert(length)
3187 assert(color)
3188
3189 local width, height
3190 if (orientation == "horizontal") then
3191 width, height = length, 1
3192 else
3193 width, height = 1, length
3194 end
3195
3196 local buffer = Buffer:new()
3197 buffer:init(width, height, path, color)
3198
3199 return buffer
3200end
3201
3202-- >> Selector
3203Objects.Selector = {}
3204Objects.Selector.draw = function(x, y, items)
3205 assert(x)
3206 assert(y)
3207 assert(items)
3208
3209 width = getLongestString(items) + 2
3210 height = #items -- Items + up and down
3211 itemCount = #items
3212 displayCount = itemCount
3213
3214 enoughXSpace = true
3215 -- determine where the selector should actually be displayed
3216 if (width > maxX) then -- Not enough monitors horizontally?
3217 x = 1
3218 enoughXSpace = false
3219 elseif (maxX - x < width) then -- Not enough space to the right.
3220 if (x >= width) then -- Let's see if there is space to the left.
3221 x = x - width
3222 else -- No space? Check where you've got more space.
3223 if (maxX / 2) > x then -- More space to the left.
3224 x = maxX - width + 1
3225 enoughXSpace = false
3226 else -- More space to the right
3227 x = 1
3228 enoughXSpace = false
3229 end
3230 end
3231 else -- Enough space to the right.
3232 x = x + 1
3233 end
3234
3235 if (height > maxY - y) then -- Not enough space from y to bottom.
3236 if ((maxY / 2) > y) then -- More space below y.
3237 if enoughXSpace then
3238 if (maxY < height) then -- Too big for the whole screen.
3239 y = 1
3240 displayCount = maxY
3241 else -- Enough space next to x and not too high.
3242 y = maxY - height
3243 end
3244 else -- Can't display it next to the selected point.
3245 y = y + 1
3246 displayCount = maxY - y + 1
3247 end
3248 else -- More space above y.
3249 if enoughXSpace then
3250 if (y < height) then -- Not enough space from top to y.
3251 if (maxY < height) then -- Too big for the whole screen.
3252 y = 1
3253 displayCount = maxY
3254 else -- Enough space next to x and not too high.
3255 y = 1
3256 end
3257 else -- Enough space from top to y.
3258 y = y - height + 1
3259 end
3260 else
3261 if (y < height) then -- Not enough space from top to y.
3262 if (maxY < height) then -- Too big for the whole screen.
3263 y = 1
3264 displayCount = maxY
3265 else -- Not enough space next to x but not too high.
3266 y = 1
3267 displayCount = y - 2
3268 end
3269 else -- Enough space from top to y.
3270 y = y - height
3271 end
3272 end
3273 end
3274 end
3275
3276 out.setBackgroundColor(ObjectColors.background)
3277
3278 local drawArrows = displayCount < height
3279
3280 local start = 1
3281 local scroll = 0
3282 local right = x + width - 1
3283 local bottom = y + height - 1
3284 local finished = false
3285 local result
3286
3287 if drawArrows then
3288 displayCount = displayCount - 2
3289 local middle = math.floor(width / 2)
3290
3291 out.setCursorPos(x + middle, y)
3292 out.write("^")
3293 out.setCursorPos(x + middle, bottom)
3294 out.write("V")
3295
3296 start = 2
3297 end
3298
3299 while not finished do
3300 out.setTextColor(ObjectColors.Editor.selectorText)
3301
3302 for row = start, displayCount do
3303 local color = (row % 2 == 0) and ObjectColors.Editor.selector1 or ObjectColors.Editor.selector2
3304 local text = items[row + scroll]
3305
3306 out.setBackgroundColor(color)
3307 out.setCursorPos(x, y + row - 1)
3308 out.write(" " .. text .. string.rep(" ", width - #text - 1))
3309 end
3310
3311 out.setBackgroundColor(ObjectColors.background)
3312
3313 local touchX, touchY, mouseButton = getCursorInput()
3314
3315 if (touchX < x or touchX > right or touchY < y or touchY > bottom) then
3316 selectedItem = nil
3317 result = false
3318 finished = true
3319 else -- User touched the selector.
3320 if showArrows then
3321 if (touchY == y) then -- up
3322 if (scroll > 0) then -- Check whether it is possible to scroll up.
3323 scroll = scroll - 1
3324 end
3325 elseif (touchY == bottom) then -- down
3326 if (displayCount < itemCount) then
3327 if (scroll < itemCount - displayCount) then
3328 scroll = scroll + 1
3329 end
3330 end
3331 end
3332 else
3333 selectedItem = items[touchY - y + scroll + 1]
3334 result = true
3335 finished = true
3336 end
3337 end
3338 end
3339
3340 drawWindow()
3341 return result
3342end
3343
3344-- >> API Functions
3345
3346-- API function: Sets the value of all variables
3347-- with the given ID.
3348function setVariableValue(variableID, newVar)
3349 VariableValues[variableID] = newVar
3350end
3351
3352-- API function: Sets the value of all progressBars
3353-- with the given ID.
3354function setProgressBarValue(objID, newVar)
3355 ProgressBarValues[objID] = newVar
3356end
3357
3358-- >> User Input Functions
3359
3360-- Gets any input of the user
3361-- (not from the environment)
3362function getAnyInput()
3363 local finished = false
3364 local event = {}
3365
3366 while not finished do
3367 finished = true
3368 os.sleep(0)
3369
3370 input = {os.pullEvent()}
3371 event.eventType = input[1]
3372 event.dragged = false
3373
3374 if (event.eventType == "monitor_touch" and not outIsTerm) then
3375 event.eventType = "mouse"
3376 event.x = input[3]
3377 event.y = input[4]
3378 event.mouseButton = 1
3379 elseif (event.eventType == "mouse_click" and outIsTerm) then
3380 event.eventType = "mouse"
3381 event.x = input[3]
3382 event.y = input[4]
3383 event.mouseButton = input[2]
3384 elseif (event.eventType == "mouse_drag") then
3385 event.eventType = "mouse"
3386 event.x = input[3]
3387 event.y = input[4]
3388 event.mouseButton = input[2]
3389 event.dragged = true
3390 elseif (event.eventType == "key") then
3391 event.key = input[2]
3392 else
3393 finished = false
3394 end
3395 end
3396
3397 return event
3398end
3399
3400-- Returns where the user clicked and which button
3401-- he pressed (always 1 if it's a monitor).
3402function getCursorInput(includeDrag)
3403 local finished = false
3404 local dragged = false -- Determines whether the event is "mouse_click" or "mouse_drag"
3405
3406 while not finished do
3407 event, param, x, y = os.pullEvent()
3408
3409 if (event == "monitor_touch" and not outIsTerm) then
3410 mouseButton = 1
3411 finished = true
3412 elseif (event == "mouse_click" and outIsTerm) then
3413 mouseButton = param
3414 finished = true
3415 elseif (event == "mouse_drag" and includeDrag) then
3416 mouseButton = param
3417 dragged = true
3418 finished = true
3419 end
3420 end
3421
3422 return x, y, mouseButton, dragged
3423end
3424
3425-- Waits until any key gets pressed.
3426function getKeyInput()
3427 os.pullEvent("key")
3428end
3429
3430function readUserInput(message, isPassword)
3431 if not outIsTerm then
3432 print(message)
3433 end
3434
3435 if isPassword then
3436 ret = read("*")
3437 else
3438 ret = read()
3439 end
3440
3441 return ret
3442end
3443
3444-- >> Display Functions
3445
3446-- Has to be used instead of paintutils.drawpixel
3447function drawPixel(x, y, color)
3448 assert(x)
3449 assert(y)
3450 assert(color)
3451
3452 out.setCursorPos(x, y)
3453 out.setBackgroundColor(color)
3454 out.write(" ")
3455end
3456
3457function drawBox(x, y, width, height, color)
3458 out.setBackgroundColor(color)
3459
3460 for row = 1, height do
3461 out.setCursorPos(x, y + row - 1)
3462 out.write(string.rep(" ", width))
3463
3464 for col = x, width do
3465 if (WindowBuffer[col] and WindowBuffer[col][y + row - 1]) then
3466 WindowBuffer[col][y + row - 1].draw = true
3467 end
3468 end
3469 end
3470end
3471
3472-- Displays the text with red background colour.
3473function drawSimpleButton(x, y, text, backgroundColor, textColor)
3474 assert(x)
3475 assert(y)
3476 assert(text)
3477
3478 backgroundColor = backgroundColor or ObjectColors.Button.default
3479 textColor = textColor or ObjectColors.Button.text
3480
3481 out.setCursorPos(x, y)
3482 out.setBackgroundColor(backgroundColor)
3483 out.setTextColor(textColor)
3484 out.write(text)
3485
3486 out.setBackgroundColor(ObjectColors.background)
3487 out.setTextColor(ObjectColors.text)
3488end
3489
3490-- Displays the default buttons.
3491function drawDefaultButtons()
3492 local window = getCurrentWindow()
3493 local button
3494
3495 if (window.showRefreshButton) then
3496 button = DefaultButtons.refresh
3497 drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.default, ObjectColors.DefaultButtons.text) -- Refresh
3498 end
3499
3500 if (window.showBackButton) then
3501 button = DefaultButtons.back
3502 drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.default, ObjectColors.DefaultButtons.text) -- Back
3503 end
3504
3505 if (window.showQuitButton ~= false) then
3506 button = DefaultButtons.quit
3507 drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.Quit.default, ObjectColors.DefaultButtons.Quit.text) -- Quit
3508 end
3509
3510 button = DefaultButtons.options
3511 if (button.required()) then
3512 drawSimpleButton(button.left, button.top, button.text, ObjectColors.DefaultButtons.default, ObjectColors.DefaultButtons.text) -- Options
3513 end
3514end
3515
3516-- Displays all objects of the window with the
3517-- ID windowID on the screen and changes the
3518-- variable "currentWindow".
3519function drawWindow(windowID)
3520 local windowContainer = getWindowContainer()
3521
3522 if windowID and windowContainer and windowContainer.Children[windowID] then
3523 currentWindow = windowID
3524 elseif currentWindow then
3525 windowID = currentWindow
3526 elseif not showCustomWindow then
3527 if Windows.startupWindow then
3528 currentWindow = Windows.startupWindow
3529 elseif (#Windows.Children ~= 0) then -- Automatically set a startup window.
3530 for windowName, window in pairs(Windows.Children) do
3531 Windows.startupWindow = windowName
3532 currentWindow = windowName
3533 break
3534 end
3535 else -- We have no windows, create the "Main" window.
3536 Objects.Container.Window.create("Main")
3537 Windows.Children.Main.isStartupWindow = true
3538 currentWindow = "Main"
3539 end
3540 else
3541 error("No current window for custom window container " .. showCustomWindow .. " found.", 1)
3542 end
3543
3544 log("Drawing window \"" .. currentWindow .. "\" with custom window \"" .. tostring(showCustomWindow) .. "\".", "INFO")
3545 local windowObject = getCurrentWindow()
3546
3547 if windowObject then
3548 WindowBuffer = Objects.Container.get(windowObject)
3549 WindowBuffer:draw()
3550 drawDefaultButtons()
3551 end
3552end
3553
3554-- >> Input Processing
3555
3556-- Works just like os.pullEvent but it only
3557-- returns custom Graffiti events.
3558-- Recommended for API usage.
3559function pullEvent(requestedEvent)
3560 if not eventTypeExists(requestedEvent) then
3561 clearScreen()
3562 print("Event type " .. tostring(requestedEvent) .. " is invalid!")
3563 print()
3564 print("Available event types:")
3565
3566 for _, event in pairs(EventTypes) do
3567 print(" " .. event)
3568 end
3569
3570 error()
3571 end
3572
3573 local finished = false
3574 local event, params
3575
3576 while not finished do
3577 event, params = getInput()
3578
3579 if event then
3580 if (requestedEvent ~= nil) then
3581 if (requestedEvent == event or event == "quit") then
3582 finished = true
3583 end
3584 else
3585 finished = true
3586 end
3587 end
3588 end
3589
3590 return event, unpack(params)
3591end
3592
3593function getInput()
3594 log("getInput", "FUNC")
3595
3596 local finished = false
3597 local event, params
3598 local x, y, mouseButton, dragged = getCursorInput(true)
3599 log("Received mouse input. X: " .. x .. ", Y: " .. y .. ", button: " .. mouseButton .. ", dragged: " .. tostring(dragged) .. ".", "INFO")
3600
3601 if not dragged then
3602 if (defaultButtonPressed("quit", x, y)) then
3603 log("Quit pressed")
3604 quit = true
3605 elseif (defaultButtonPressed("refresh", x, y)) then
3606 log("Refresh pressed")
3607 drawWindow()
3608 finished = true
3609 elseif (defaultButtonPressed("back", x, y)) then
3610 log("Back pressed")
3611
3612 local windowObject = getCurrentWindow()
3613
3614 if (windowObject.parent ~= nil) then
3615 drawWindow(windowObject.parent)
3616 finished = true
3617 else
3618 finished = true
3619 end
3620 end
3621 end
3622
3623 if finished then
3624 return nil
3625 elseif quit then
3626 return "quit", { "Graffiti" } -- Used for the API
3627 end
3628
3629 local param
3630 local path = WindowBuffer.bufferTable[x][y].path
3631
3632 if path and #path > 0 then
3633 local object = Path.getObject(path)
3634 local clickX, clickY = Path.getRelativePos(path, x, y)
3635 local modX, modY = Objects.getPosModifier(object)
3636 clickX, clickY = clickX + (modX * -1), clickY + (modY * -1)
3637 clickX, clickY = clickX - object.x + 1, clickY - object.y + 1
3638
3639 if dragged then
3640 event, params = Objects.drag(object, clickX, clickY)
3641 else
3642 event, params = Objects.click(object, clickX, clickY)
3643 end
3644 end
3645
3646 return event, params
3647end
3648
3649-- Shows the message on the computer for debugging.
3650function debugMessage(message)
3651 if outIsTerm then
3652 error("Can't display a debug message on a computer!")
3653 end
3654
3655 print(message)
3656end
3657
3658function splitAt(self, delimiter)
3659 delimiterPos = string.find(self, delimiter)
3660 left = string.sub(self, 1, delimiterPos - 1)
3661 right = string.sub(self, delimiterPos + #delimiter)
3662
3663 return left, right
3664end
3665
3666-- >>> Custom Windows
3667
3668CustomWindows.init = function()
3669 log("Initializing custom windows...", "INFO")
3670
3671 for windowName, window in pairs(CustomWindows) do
3672 if (type(window) == "table" and window.init) then
3673 log("Initializing custom window \"" .. windowName .. "\".", "INFO")
3674 window.init()
3675 end
3676 end
3677
3678 showCustomWindow = nil
3679 currentWindow = nil
3680end
3681
3682CustomWindows.draw = function(containerName, windowName)
3683 assert(containerName)
3684 assert(windowName)
3685
3686 log("Drawing custom window \"" .. windowName .. "\" from container \"" .. containerName .. "\".", "INFO")
3687
3688 showCustomWindow = containerName
3689 drawWindow(windowName)
3690 local result = nil
3691
3692 while result == nil do
3693 local event, params = getInput()
3694
3695 if (event == "button_clicked") then
3696 local objID = params[1]
3697
3698 if (objID == "apply") then
3699 result = true
3700 elseif (objID == "cancel") then
3701 result = false
3702 end
3703 elseif (event == "quit") then
3704 result = true
3705 quit = false
3706 end
3707 end
3708
3709 clearScreen()
3710 showCustomWindow = nil
3711
3712 return result
3713end
3714
3715-- >> Editor Windows
3716CustomWindows.Editor = {}
3717
3718CustomWindows.Editor.init = function()
3719 showCustomWindow = "Editor"
3720 CustomWindows.Editor.Children = {}
3721
3722 -- Main window
3723 Objects.Container.Window.create("Main")
3724 currentWindow = "Main"
3725
3726 Objects.createCustom("ScrollView", 2, 2, { objID="windowListContainer", width=30, height=maxY-2, scrollXEnabled=true, scrollYEnabled=true } )
3727 Objects.createCustom("List", 4, 4, { objID="WindowList", items={}, load=CustomFunctions.Editor.getWindowList, isMultiselect=false } )
3728
3729 Objects.createCustom("Panel", 34, 2, { objID="newWindow", width=15, height=7 } )
3730 Objects.createCustom("Input", 36, 4, { objID="newWindow", message="Enter a name for the new window." } )
3731 Objects.createCustom("Button", 36, 6, { objID="newWindow", width=8, height=1, text="New", funcType="function" } )
3732 Objects.createCustom("Button", 36, 7, { objID="renameWindow", width=8, height=1, text="Rename", funcType="function" } )
3733
3734 Objects.createCustom("Button", 34, 10, { objID="editWindow", width=17, height=1, text="Edit", funcType="function" } )
3735 Objects.createCustom("Button", 34, 12, { objID="deleteWindow", width=17, height=1, text="Delete", funcType="function" } )
3736 Objects.createCustom("Button", 34, 14, { objID="setParent", width=17, height=1, text="Set parent", funcType="function" } )
3737 Objects.createCustom("Button", 34, 16, { objID="setStartup", width=17, height=1, text="Startup window", funcType="function" } )
3738end
3739
3740CustomFunctions.Editor = {}
3741
3742CustomFunctions.Editor.getWindowList = function()
3743 local ret = {}
3744
3745 for key, value in pairs(Windows.Children) do
3746 table.insert(ret, key)
3747 end
3748
3749 table.sort(ret)
3750
3751 return ret
3752end
3753
3754CustomFunctions.Editor.getWindowListObject = function()
3755 return CustomWindows.Editor.Children.Main.Children[1].Children[1]
3756end
3757
3758CustomFunctions.Editor.editLastWindow = function()
3759
3760 if not lastWindow then
3761 if Windows.startupWindow then
3762 showCustomWindow = nil
3763 lastWindow = Windows.startupWindow
3764 else
3765 return
3766 end
3767 end
3768
3769 changeButtonColor = false
3770 drawWindow(lastWindow)
3771end
3772
3773-- Creates a new window. The user has to enter the window name in the computer.
3774CustomFunctions.Editor.newWindow = function()
3775 local windowName = ObjectData.Input["newWindow"]
3776 local list = CustomFunctions.Editor.getWindowListObject()
3777
3778 if (windowName == nil or windowName == "") then
3779 return
3780 elseif (Windows.Children[windowName] == nil) then
3781 showCustomWindow = nil
3782 Objects.Container.Window.create(windowName)
3783 changeButtonColor = false
3784 showCustomWindow = "Editor"
3785 end
3786
3787 ListEditorList = list.Items
3788 ObjectData.Input["newWindow"] = ""
3789 drawWindow()
3790end
3791
3792CustomFunctions.Editor.renameWindow = function()
3793 local newWindowName = ObjectData.Input["newWindow"]
3794 local list = CustomFunctions.Editor.getWindowListObject()
3795 local key = Objects.List.getFirstSelectedValue(list)
3796
3797 if (value and not table.contains(windowList, newWindowName)) then
3798 local oldWindowName = value
3799
3800 Files.rename({ Files.Project.subDir, currentProject }, oldWindowName .. Files.Project.extension, newWindowName .. Files.Project.extension)
3801 Windows.Children[newWindowName] = Windows.Children[oldWindowName]
3802 Windows.Children[oldWindowName] = nil
3803 end
3804
3805 drawWindow()
3806end
3807
3808-- Edits the first selected window.
3809CustomFunctions.Editor.editWindow = function()
3810 local list = CustomFunctions.Editor.getWindowListObject()
3811 local key = Objects.List.getFirstSelectedKey(list)
3812
3813 if not key then
3814 return
3815 end
3816
3817 showCustomWindow = nil
3818 lastWindow = list.Items[key]
3819 changeButtonColor = false
3820 drawWindow(lastWindow)
3821end
3822
3823-- Deletes the selected window(s).
3824CustomFunctions.Editor.deleteWindow = function()
3825 local list = CustomFunctions.Editor.getWindowListObject()
3826
3827 for key, selected in pairs(ObjectData.List[list.objID]) do
3828 if (selected == true) then
3829 local windowName = list.Items[key]
3830 Files.remove(Files.Project.subDir, fs.combine(currentProject, windowName .. Files.Project.extension))
3831 Windows.Children[windowName] = nil
3832 end
3833 end
3834
3835 drawWindow()
3836 ListEditorList = list.Items
3837end
3838
3839-- Sets the first selected window as the one that
3840-- should be displayed on startup.
3841CustomFunctions.Editor.setStartup = function()
3842 local list = CustomFunctions.Editor.getWindowListObject()
3843 local key = Objects.List.getFirstSelectedKey(list)
3844 local selectedWindowName = list.Items[key]
3845
3846 for windowName, window in pairs(Windows.Children) do
3847 window.isStartupWindow = (windowName == selectedWindowName)
3848 end
3849end
3850
3851-- Let's the user define the parent-attribute of the current window.
3852CustomFunctions.Editor.setParent = function()
3853 local list = CustomFunctions.Editor.getWindowListObject()
3854 local selected = Objects.List.getFirstSelectedKey(list)
3855 local buffer = Objects.Line.get("vertical", list.height, ObjectColors.background, {1, 99})
3856
3857 local x, y, mouseButton = getCursorInput()
3858 local modX, modY = Objects.getPosModifier(list)
3859 x, y = x + (modX * -1), y + (modY * -1)
3860 local selectedParent = y - list.absoluteY + 1
3861
3862 if (selectedParent >= 1 and selectedParent <= list.height) then -- Clicked inside the list.
3863 if (selectedParent ~= selected) then -- Selected parentWindow is not selected window.
3864 Windows.Children[list.Items[selected]].parent = list.Items[selectedParent]
3865 end
3866 end
3867
3868 for i = 1, list.height do
3869 drawPixel(1, i + list.absoluteY - 1, ObjectColors.background)
3870 end
3871end
3872
3873-- >> List Editor Windows
3874CustomWindows.ListEditor = {}
3875
3876CustomWindows.ListEditor.init = function()
3877 showCustomWindow = "ListEditor"
3878 CustomWindows.ListEditor.Children = {}
3879
3880 Objects.Container.Window.create("Main")
3881 CustomWindows.ListEditor.Children["Main"].showQuitButton = false
3882 currentWindow = "Main"
3883
3884 Objects.createCustom("ScrollView", 2, 2, { objID="listContainer", width=22, height=maxY - 3, scrollXEnabled=true, scrollYEnabled=true } )
3885 Objects.createCustom("List", 4, 4, { objID="itemList", isMultiselect=true, items={}, load=CustomFunctions.ListEditor.getListEditorItems } )
3886
3887 Objects.createCustom("Panel", 26, 2, { objID="newEntryPanel", width=16, height=7 } )
3888 Objects.createCustom("Input", 28, 4, { objID="newEntry" } )
3889 Objects.createCustom("Button", 28, 6, { objID="newEntry", width=11, height=1, text="New", funcType="function" } )
3890 Objects.createCustom("Button", 28, 7, { objID="renameEntry", width=11, height=1, text="Rename", funcType="function" } )
3891
3892 Objects.createCustom("Text", 26, 11, { objID="editEntryText", text="Edit entries" } )
3893 Objects.createCustom("Panel", 26, 12, { objID="editEntryPanel", width=16, height=8 } )
3894 Objects.createCustom("Button", 28, 14, { objID="moveEntryUp", width=11, height=1, text="Move up", funcType="function" } )
3895 Objects.createCustom("Button", 28, 15, { objID="moveEntryDown", width=11, height=1, text="Move down", funcType="function" } )
3896 Objects.createCustom("Button", 28, 17, { objID="removeEntry", width=11, height=1, text="Remove", funcType="function" } )
3897
3898 Objects.createCustom("Button", 43, 12, { objID="apply", width=8, height=3, text="OK", funcType="function" } )
3899 Objects.createCustom("Button", 43, 16, { objID="cancel", width=8, height=3, text="Cancel", funcType="function" } )
3900end
3901
3902CustomFunctions.ListEditor = {}
3903
3904CustomFunctions.ListEditor.getListEditorItems = function()
3905 local tempList = {}
3906
3907 for _, value in ipairs(ListEditorList) do
3908 table.insert(tempList, value)
3909 end
3910
3911 return tempList
3912end
3913
3914CustomFunctions.ListEditor.newEntry = function()
3915 local input = ObjectData.Input["newEntry"]
3916
3917 if (input == nil or input == "") then
3918 return
3919 end
3920
3921 table.insert(ListEditorList, input)
3922 ObjectData.Input["newEntry"] = nil
3923 drawWindow()
3924end
3925
3926CustomFunctions.ListEditor.renameEntry = function()
3927 local selectedItems = ObjectData.List["itemList"]
3928 local input = ObjectData.Input["newEntry"]
3929
3930 if (input == nil or input == "") then
3931 return
3932 end
3933
3934 for key, value in pairs(selectedItems) do
3935 if value then -- Item is selected
3936 ListEditorList[key] = input
3937 end
3938 end
3939
3940 ObjectData.Input["newEntry"] = nil
3941 drawWindow()
3942end
3943
3944CustomFunctions.ListEditor.moveEntryUp = function()
3945 if not ObjectData.List["itemList"] then
3946 return
3947 end
3948
3949 local lastIndex = nil
3950 local selectedItems = ObjectData.List["itemList"]
3951 local lock = nil
3952
3953 for key, value in pairs(ListEditorList) do
3954 local selected = selectedItems[key] == true
3955
3956 if lastIndex then
3957 if selected then
3958 if (lock ~= lastIndex) then
3959 selectedItems[lastIndex], selectedItems[key] =
3960 selectedItems[key], selectedItems[lastIndex]
3961
3962 ListEditorList[lastIndex], ListEditorList[key] =
3963 ListEditorList[key], ListEditorList[lastIndex]
3964 else
3965 lock = key
3966 end
3967 end
3968 elseif selected then
3969 lock = key
3970 end
3971
3972 lastIndex = key
3973 end
3974
3975 drawWindow()
3976end
3977
3978CustomFunctions.ListEditor.moveEntryDown = function()
3979 if not ObjectData.List["itemList"] then
3980 return
3981 end
3982
3983 local lastIndex = nil
3984 local selectedItems = ObjectData.List["itemList"]
3985 local lock = nil
3986
3987 for key = #ListEditorList, 1, -1 do
3988 local selected = selectedItems[key] == true
3989
3990 if lastIndex then
3991 if selected then
3992 if (lock ~= lastIndex) then
3993 selectedItems[lastIndex], selectedItems[key] =
3994 selectedItems[key], selectedItems[lastIndex]
3995
3996 ListEditorList[lastIndex], ListEditorList[key] =
3997 ListEditorList[key], ListEditorList[lastIndex]
3998 else
3999 lock = key
4000 end
4001 end
4002 elseif selected then
4003 lock = key
4004 end
4005
4006 lastIndex = key
4007 end
4008
4009 drawWindow()
4010end
4011
4012CustomFunctions.ListEditor.removeEntry = function()
4013 local selectedItems = ObjectData.List["itemList"]
4014 local ret = {}
4015
4016 for key, value in pairs(ListEditorList) do
4017 local selected = (selectedItems[key] == true)
4018
4019 if not selected then
4020 table.insert(ret, ListEditorList[key])
4021 end
4022 end
4023
4024 ObjectData.List["itemList"] = {}
4025 ListEditorList = ret
4026
4027 drawWindow()
4028end
4029
4030-- >> Setup Windows
4031CustomWindows.Setup = {}
4032
4033CustomWindows.Setup.init = function()
4034 showCustomWindow = "Setup"
4035 CustomWindows.Setup.Children = {}
4036
4037 Objects.Container.Window.create("Main")
4038 currentWindow = "Main"
4039
4040 Objects.createCustom("Text", 2, 2, { objID="welcomeText", text=Text.setupText1 } )
4041 Objects.createCustom("Text", 2, 3, { objID="welcomeText", text=Text.setupText2 } )
4042 Objects.createCustom("Text", 2, 5, { objID="welcomeText", text=Text.setupText3 } )
4043 Objects.createCustom("Text", 2, 6, { objID="welcomeText", text=Text.setupText4 } )
4044 Objects.createCustom("Text", 2, 7, { objID="welcomeText", text=Text.setupText5 } )
4045
4046 Objects.createCustom("Button", 2, 9, { objID="nextWindow", text=Text.next, width=6, height=3, funcType="switch", window="Setup1" } )
4047 Objects.createCustom("Button", 2, 13, { objID="cancel", text=Text.skipSetup, width=36, height=3, funcType="function" } )
4048
4049 Objects.Container.Window.create("Setup1")
4050 CustomWindows.Setup.Children["Setup1"].showBackButton = true
4051 CustomWindows.Setup.Children["Setup1"].parent = "Main"
4052 currentWindow = "Setup1"
4053
4054 Objects.createCustom("Text", 2, 2, { objID="chooseSettings", text=Text.chooseSettings1 } )
4055
4056 Objects.createCustom("Text", 2, 4, { objID="chooseLanguage", text=Text.language .. ":" } )
4057 Objects.createCustom("ScrollView", 2, 5, { objID="Language", width=14, height=maxY - 6, scrollXEnabled=true, scrollYEnabled=true } )
4058 CustomWindows.Setup.languageList = Objects.createCustom("List", 4, 7, { objID="languages", load=CustomFunctions.Setup.getLanguageList } )
4059
4060 Objects.createCustom("Text", 17, 4, { objID="chooseColorTheme", text=Text.colorTheme .. ":" } )
4061 Objects.createCustom("ScrollView", 17, 5, { objID="colorTheme", width=14, height=maxY - 6, scrollXEnabled=true, scrollYEnabled=true } )
4062 CustomWindows.Setup.colorThemeList = Objects.createCustom("List", 19, 7, { objID="colorTheme", load=CustomFunctions.Setup.getColorThemeList } )
4063
4064 Objects.createCustom("RadioButton", 34, 4, { objID="showDataFolder", text=Text.showDataFolder, defaultIsChecked=true } )
4065 Objects.createCustom("RadioButton", 34, 6, { objID="hideDataFolder", text=Text.hideDataFolder } )
4066
4067 Objects.createCustom("Button", 34, 16, { objID="apply", text=Text.finished, width=15, height=3, funcType="function" } )
4068end
4069
4070CustomFunctions.Setup = {}
4071
4072CustomFunctions.Setup.getLanguageList = function()
4073 local ret = {}
4074
4075 for key, value in pairs(Languages) do
4076 table.insert(ret, key)
4077 end
4078
4079 table.sort(ret)
4080
4081 return ret
4082end
4083
4084CustomFunctions.Setup.getColorThemeList = function()
4085 local ret = {}
4086
4087 for key, value in pairs(ColorThemes) do
4088 table.insert(ret, key)
4089 end
4090
4091 table.sort(ret)
4092
4093 return ret
4094end
4095
4096-- Runs the setup.
4097function runSetup(useDefaultSettings)
4098 if not useDefaultSettings then
4099 initDefaultButtons()
4100 CustomWindows.Setup.init()
4101 CustomWindows.draw("Setup", "Main")
4102 end
4103
4104 local languageListObject = CustomWindows.Setup.languageList
4105 local colorThemeListObject = CustomWindows.Setup.colorThemeList
4106
4107 local selectedLanguage = Objects.List.getFirstSelectedValue(languageListObject)
4108 local selectedColorTheme = Objects.List.getFirstSelectedValue(colorThemeListObject)
4109
4110 if selectedLanguage and Languages[selectedLanguage] then
4111 Settings.language = selectedLanguage
4112 end
4113
4114 if (selectedColorTheme and ColorThemes[selectedColorTheme]) then
4115 Settings.colorTheme = selectedColorTheme
4116 end
4117
4118 Settings.hideDataFolder = ObjectData.RadioButton["hideDataFolder"] == true
4119
4120 dataFolderPath = fs.combine(root, (Settings.hideDataFolder and ".GraffitiData" or "GraffitiData"))
4121
4122 if not fs.exists(dataFolderPath) then
4123 fs.makeDir(dataFolderPath)
4124 end
4125
4126 Files.save()
4127end
4128
4129-- Shows lines marking the top left part of an
4130-- object as well as well as pixels displaying
4131-- the alignment of an object.
4132function drawAlignmentLines(object, left, top, right, bottom)
4133 local color = ObjectColors.Editor.marker
4134 local moveX, moveY = Objects.getMovePos(object)
4135
4136 -- Draw the lines.
4137 Objects.Line.draw(left - 1, moveY, "left", left - 2, color) -- left
4138 Objects.Line.draw(moveX, top -1, "up", top - 2, color) -- up
4139 Objects.Line.draw(right + 1, moveY, "right", maxX - (right + 1), color) -- right
4140 Objects.Line.draw(moveX, bottom + 1, "down", maxY - (bottom + 1), color) -- down
4141
4142 -- Display the alignment-pixels.
4143 horizontalAlignment = object.horizontalAlignment
4144 verticalAlignment = object.verticalAlignment
4145
4146 if (horizontalAlignment == "left" or horizontalAlignment == "stretch") then -- left
4147 drawPixel(1, moveY, ObjectColors["Editor"].alignmentTrue)
4148 else
4149 drawPixel(1, moveY, ObjectColors["Editor"].alignmentFalse)
4150 end
4151
4152 if (horizontalAlignment == "right" or horizontalAlignment == "stretch") then -- right
4153 drawPixel(maxX, moveY, ObjectColors["Editor"].alignmentTrue)
4154 else
4155 drawPixel(maxX, moveY, ObjectColors["Editor"].alignmentFalse)
4156 end
4157
4158 if (verticalAlignment == "top" or verticalAlignment == "stretch") then -- top
4159 drawPixel(moveX, 1, ObjectColors["Editor"].alignmentTrue)
4160 else
4161 drawPixel(moveX, 1, ObjectColors["Editor"].alignmentFalse)
4162 end
4163
4164 if (verticalAlignment == "bottom" or verticalAlignment == "stretch") then -- bottom
4165 drawPixel(moveX, maxY, ObjectColors["Editor"].alignmentTrue)
4166 else
4167 drawPixel(moveX, maxY, ObjectColors["Editor"].alignmentFalse)
4168 end
4169
4170 out.setBackgroundColor(ObjectColors.background)
4171end
4172
4173-- Returns the values of horizontalAlignment and
4174-- verticalAlignment depending which sides are set
4175-- to true.
4176function getAlignment(left, top, right, bottom)
4177 local retHorizontal, retVertical = "left", "top"
4178
4179 if right then
4180 if left then
4181 retHorizontal = "stretch"
4182 else
4183 retHorizontal = "right"
4184 end
4185 else
4186 retHorizontal = "left"
4187 end
4188
4189 if bottom then
4190 if top then
4191 retVertical = "stretch"
4192 else
4193 retVertical = "bottom"
4194 end
4195 else
4196 retVertical = "top"
4197 end
4198
4199 return retHorizontal, retVertical
4200end
4201
4202function markSelectedObject()
4203 if not selectedObject then
4204 return
4205 end
4206
4207 Objects.draw(selectedObject, nil, true) -- Draw the object with its markers.
4208
4209 local moveX, moveY = Objects.getMovePos(selectedObject)
4210 drawPixel(moveX, moveY, (currentEditorAction == "Move" and not selectedObjectDragged) and ObjectColors.Editor.active or ObjectColors.Editor.move)
4211
4212 if selectedObject.canScale then
4213 local scaleX, scaleY = Objects.getScalePos(selectedObject)
4214 drawPixel(scaleX, scaleY, (currentEditorAction == "Scale" and not selectedObjectDragged) and ObjectColors.Editor.active or ObjectColors.Editor.scale)
4215 end
4216
4217 out.setBackgroundColor(ObjectColors.background)
4218end
4219
4220-- Let's the user delete an object or change its attributes depending on the current edit-mode.
4221function editObject(object, x, y, dragged)
4222 assert(object)
4223 log("Editing " .. object.objType .. ", ID: " .. object.objID .. ", x: " .. x .. ", y: " .. y .. ", dragged: " .. tostring(dragged) .. ".")
4224
4225 local relX, relY = Path.getRelativePos(object.path, x, y)
4226 local modX, modY = Objects.getPosModifier(object)
4227 local left, top, right, bottom = Objects.getDimensions(object)
4228 left, top, right, bottom = left + modX, top + modY, right + modX, bottom + modY
4229
4230 local moveX, moveY = Objects.getMovePos(object)
4231
4232 local scaleX, scaleY
4233 if object.canScale then
4234 scaleX, scaleY = Objects.getScalePos(object)
4235 end
4236
4237 if currentEditorAction then
4238 if (currentEditorAction == "Move") then
4239 addX = x - moveX
4240 addY = y - moveY
4241 Objects.move(object, addX, addY)
4242 elseif (currentEditorAction == "Scale") then
4243 Objects.scale(object, relX + (modX * -1), relY + (modY * -1))
4244 else
4245 error("Unknown editor action \"" .. tostring(currentEditorAction) .. "\".", 1)
4246 end
4247
4248 if dragged then
4249 selectedObjectDragged = true
4250 else
4251 currentEditorAction = nil
4252 end
4253 else
4254 if (x == moveX and y == moveY) then
4255 currentEditorAction = "Move"
4256 elseif (object.canScale and x == scaleX and y == scaleY) then
4257 currentEditorAction = "Scale"
4258 else
4259 relX, relY = relX - object.x + 1, relY - object.y + 1
4260
4261 if not (Objects.editorClick(object, relX + modX, relY + modY)) then
4262 if (not outIsTerm and Objects.Selector.draw(x, y, RightClickActions)) then
4263 if (selectedItem == "Attributes") then
4264 Objects.editAttributes(object)
4265 elseif (selectedItem == "Delete") then
4266 Objects.remove(object, "Delete")
4267 drawWindow()
4268 end
4269 end
4270 end
4271 end
4272 end
4273
4274 out.setBackgroundColor(ObjectColors.background)
4275 drawWindow()
4276end
4277
4278function markVariables(container)
4279 assert(container)
4280
4281 for _, object in pairs(container.Children) do
4282 if (object.isContainer) then
4283 markVariables(object)
4284 elseif (object.objType == "Variable") then
4285 local x, y = Objects.getAbsolutePos(object)
4286 drawPixel(x, y, ObjectColors.Editor.marker)
4287 out.setBackgroundColor(ObjectColors.background)
4288 end
4289 end
4290end
4291
4292function markDefaultButtons()
4293 local window = getCurrentWindow()
4294
4295 -- refresh button
4296 local refresh = DefaultButtons.refresh
4297 out.setCursorPos(refresh.left, refresh.top)
4298 if (window.showRefreshButton) then
4299 out.setBackgroundColor(ObjectColors.Button.default)
4300 out.write(refresh.text)
4301 else
4302 out.setBackgroundColor(ObjectColors.Editor.marker)
4303 out.write(string.rep(" ", #refresh.text))
4304 end
4305
4306 -- back button
4307 local back = DefaultButtons.back
4308 out.setCursorPos(back.left, back.top)
4309 if (window.showBackButton) then
4310 out.setBackgroundColor(ObjectColors.Button.default)
4311 out.write(back.text)
4312 else
4313 out.setBackgroundColor(ObjectColors.Editor.marker)
4314 out.write(string.rep(" ", #back.text))
4315 end
4316
4317 out.setBackgroundColor(ObjectColors.background)
4318end
4319
4320function getEditorInput()
4321 log("getEditorInput", "FUNC")
4322
4323 local event
4324 local x, y, mouseButton, dragged
4325
4326 if showCustomWindow ~= "Editor" then
4327 drawWindow()
4328 markDefaultButtons()
4329 markSelectedObject()
4330
4331 event = getAnyInput()
4332
4333 if (event.eventType == "mouse") then
4334 x, y, mouseButton, dragged = event.x, event.y, event.mouseButton, event.dragged
4335 log("Received mouse input. X: " .. x .. ", Y: " .. y .. ", button: " .. mouseButton .. ".")
4336 end
4337 end
4338
4339 if (showCustomWindow ~= "Editor" and event.eventType == "key") then
4340 callShortcut(event.key)
4341 elseif (showCustomWindow == "Editor" or defaultButtonPressed("options", x, y)) then
4342 selectedObject = nil
4343 showCustomWindow = "Editor"
4344 drawWindow("Main")
4345
4346 while showCustomWindow == "Editor" and not quit do
4347 getInput()
4348 end
4349 elseif (not dragged and defaultButtonPressed("quit", x, y)) then
4350 quit = true
4351 elseif (not dragged and defaultButtonPressed("refresh", x, y)) then
4352 Windows.Children[currentWindow].showRefreshButton = not Windows.Children[currentWindow].showRefreshButton
4353 elseif (not dragged and defaultButtonPressed("back", x, y)) then
4354 Windows.Children[currentWindow].showBackButton = not Windows.Children[currentWindow].showBackButton
4355 else
4356 if dragged then
4357 if (selectedObject == nil) then
4358 return
4359 else
4360 editObject(selectedObject, x, y, dragged)
4361 end
4362 else
4363 if selectedObjectDragged then
4364 selectedObjectDragged = false
4365 currentEditorAction = nil
4366 end
4367
4368 local container = getCurrentWindow()
4369 local path = WindowBuffer.bufferTable[x][y].path
4370
4371 if (path == nil or #path == 0) then -- No object touched.
4372 if selectedObject then
4373 if currentEditorAction then
4374 editObject(selectedObject, x, y)
4375 end
4376
4377 selectedObject = nil
4378 else
4379 drawPixel(x, y, ObjectColors.Editor.new)
4380 if (Objects.Selector.draw(x, y, ObjectTypes)) then -- Draw selector for new object.
4381 Objects.create(selectedItem, x, y)
4382 end
4383 end
4384 else
4385 local object = Path.getObject(path)
4386
4387 if (mouseButton == 1) then
4388 if (selectedObject and Objects.isClicked(selectedObject, x, y)) then
4389 editObject(selectedObject, x, y, dragged)
4390 else
4391 selectedObject = object
4392 end
4393 else
4394 if selectedObject then
4395 selectedObject = nil
4396 elseif (Objects.Selector.draw(x, y, RightClickActions)) then
4397 if (selectedItem == "Attributes") then
4398 Objects.editAttributes(object)
4399 elseif (selectedItem == "Delete") then
4400 Objects.remove(object, "Delete")
4401 end
4402 end
4403 end
4404 end
4405 end
4406 end
4407end
4408
4409-- Runs Graffiti in editMode.
4410function windowEditor()
4411 editMode = true
4412
4413 showCustomWindow = "Editor"
4414
4415 while not quit do
4416 getEditorInput()
4417 end
4418end
4419
4420function round(number)
4421 assert(number)
4422 comma = number % 1
4423 if comma < 0.5 then
4424 ret = math.floor(number)
4425 else
4426 ret = math.ceil(number)
4427 end
4428
4429 return ret
4430end
4431
4432function printInfo()
4433 print()
4434 print(version)
4435 print("Author: Encreedem")
4436 print()
4437 print("Param(s):")
4438 print("info - Shows some info about the program... but I guess you know that already.")
4439 print("edit - Starts the program in edit-mode.")
4440 print()
4441 print("Visit the CC-forums or my YouTube channel (Encreedem CP) for news and help.")
4442end
4443
4444-- Gets called when Graffiti gets the argument "test"
4445function testMethod()
4446 error("Nothing to test...", 2)
4447end
4448
4449-- >>> initialization
4450
4451-- Sets the "startupWindow" variable to the first
4452-- window with enabled "isStartupWindow" attribute.
4453function getStartupWindow()
4454 for windowName, window in pairs(Windows.Children) do
4455 if window.isStartupWindow then
4456 startupWindow = windowName
4457 return
4458 end
4459 end
4460end
4461
4462-- Initializes the default buttons.
4463-- (Quit, Back, Refresh, Options)
4464function initDefaultButtons()
4465 DefaultButtons.quit = {
4466 text=Text.quit,
4467 left=maxX - string.len(Text.quit) + 1,
4468 top=1,
4469 right=maxX,
4470 bottom=1,
4471 required = function()
4472 return getCurrentWindow().showQuitButton ~= false
4473 end
4474 }
4475
4476 DefaultButtons.back = {
4477 text = Text.back,
4478 left = 1,
4479 top = 1,
4480 right = string.len(Text.back),
4481 bottom = 1,
4482 required = function()
4483 return getCurrentWindow().showBackButton
4484 end
4485 }
4486
4487 DefaultButtons.refresh = {
4488 text = Text.refresh,
4489 left = maxX - string.len(Text.refresh) + 1,
4490 top = maxY,
4491 right = maxX,
4492 bottom = maxY,
4493 required = function()
4494 return (getCurrentWindow().showRefreshButton or (editMode and not showEdtorOptions))
4495 end
4496 }
4497
4498 DefaultButtons.options = {
4499 text = Text.options,
4500 left = 1,
4501 top = maxY,
4502 right = string.len(Text.options),
4503 bottom = maxY,
4504 required=function()
4505 return (editMode and showCustomWindow ~= "Editor")
4506 end
4507 }
4508end
4509
4510-- Initializes all windows to allow Graffiti to
4511-- use them as if they were objects.
4512function initWindows()
4513 for _, window in pairs(Windows.Children) do
4514 window.width, window.height = maxX, maxY
4515 end
4516end
4517
4518-- Tells the user that the monitor or computer
4519-- doesn't support colors.
4520function showColorWarning()
4521 out.clear()
4522 out.setCursorPos(2, 2)
4523 out.write("This computer/monitor does not support colors!")
4524
4525 local state = 0
4526 local move = "I don't know this move!"
4527 local finished = false
4528 while not finished and not quit do
4529 out.setCursorPos(1, 4)
4530 out.clearLine()
4531 out.setCursorPos(2, 4)
4532
4533 if (state == 0) then
4534 move = "<( \" <) <( \" <) <( \" <)"
4535 elseif (state == 1 or state == 3 or state == 5) then
4536 move = " (^\"^) (^\"^) (^\"^)"
4537 elseif (state == 2) then
4538 move = " (> \" )> (> \" )> (> \" )>"
4539 elseif (state == 4) then
4540 move = " (> \" )><( \" )><( \" <)"
4541 elseif (state == 6) then
4542 move = "<( \" <) (>\"<) (> \" )>"
4543 elseif (state == 7) then
4544 move = " (v''v) (v''v) (v''v)"
4545 else
4546 error("Unable to show you that you need an advanced computer/monitor in a fancy way!")
4547 end
4548
4549 out.write(move)
4550 state = (state + 1) % 8
4551 os.sleep(0.25)
4552 end
4553end
4554
4555-- Checks if the monitor on monitorSide exists and wraps it into "monitor".
4556function getOutput()
4557 if (monitor == nil and outIsTerm == false) then
4558 local monitorFound = false
4559 for _, side in pairs(Sides) do
4560 if (peripheral.getType(side) == "monitor") then
4561 monitor = peripheral.wrap(side)
4562 monitorFound = true
4563 out = monitor
4564 outIsTerm = false
4565 end
4566 end
4567
4568 if not monitorFound then
4569 out = term
4570 outIsTerm = true
4571 end
4572 elseif outIsTerm then
4573 out = term
4574 else
4575 out = monitor
4576 end
4577end
4578
4579-- Initializes Graffitis' variables.
4580function init()
4581 getOutput()
4582
4583 maxX, maxY = out.getSize()
4584 if (maxX < 16 or maxY < 10) then -- smaller than 2x2
4585 print("Screen too small! You need at least 2x2 monitors!")
4586 return false
4587 elseif not out.isColor() then
4588 parallel.waitForAny(showColorWarning, getKeyInput)
4589 clearScreen()
4590 return false
4591 end
4592
4593 isAPI = (shell == nil)
4594
4595 initDone = true
4596 return true
4597end
4598
4599function checkArgs()
4600 doCall = main
4601 arg = Args[1]
4602
4603 if (arg ~= nil) then
4604 if (arg == "edit") then
4605 doCall = windowEditor
4606 elseif (arg == "info") then
4607 doCall = printInfo
4608 elseif (arg == "term") then
4609 outIsTerm = true
4610 elseif (arg == "test") then
4611 doCall = testMethod
4612 end
4613 end
4614
4615 doCall()
4616end
4617
4618-- Calls the "getInput" function until the user presses the quit-button.
4619function main()
4620 drawWindow(startupWindow)
4621
4622 while not quit do
4623 getInput()
4624 end
4625end
4626
4627if init() then
4628 Files.init()
4629 Files.clear(Files.Log.subDir, Files.Log.name)
4630 logFileLoaded = true
4631 log("Graffiti initialized.")
4632
4633 getStartupWindow()
4634 initWindows()
4635 CustomWindows.init()
4636 initDefaultButtons()
4637 Objects.init()
4638
4639 if not isAPI then
4640 checkArgs() -- Calls the "main" function. Everything below happens after closing Graffiti.
4641
4642 -- Closing Program
4643 if editMode and saveAfterQuit then
4644 Files.save()
4645 end
4646
4647 out.setBackgroundColor(colors.black)
4648 out.setTextColor(colors.white)
4649 out.clear()
4650 out.setCursorPos(1, 1)
4651 else
4652 log("Graffiti loaded as an API.")
4653 end
4654else
4655 error("Graffiti Initialization failed!")
4656end