· 5 years ago · Oct 26, 2020, 11:26 PM
1--Bedrock Build: 483
2--This code is squished down in to one, rather hard to read file.
3--As such it is not much good for anything other than being loaded as an API.
4--If you want to look at the code to learn from it, copy parts or just take a look,
5--you should go to the GitHub repo. http://github.com/oeed/Bedrock/
6
7--
8-- Bedrock is the core program framework used by all OneOS and OneCode programs.
9-- Inspired by Apple's Cocoa framework.
10-- (c) oeed 2014
11--
12-- For documentation see the Bedrock wiki, github.com/oeed/Bedrock/wiki/
13--
14
15local apis = {
16["Drawing"] = [[
17local round = function(num, idp)
18 local mult = 10^(idp or 0)
19 return math.floor(num * mult + 0.5) / mult
20end
21
22local _w, _h = term.getSize()
23local copyBuffer = nil
24
25Screen = {
26 Width = _w,
27 Height = _h
28}
29
30Constraints = {
31
32}
33
34CurrentConstraint = {1,1,_w,_h}
35IgnoreConstraint = false
36
37function AddConstraint(x, y, width, height)
38 local x2 = x + width - 1
39 local y2 = y + height - 1
40 table.insert(Drawing.Constraints, {x, y, x2, y2})
41 Drawing.GetConstraint()
42end
43
44function RemoveConstraint()
45 --table.remove(Drawing.Constraints, #Drawing.Constraints)
46 Drawing.Constraints[#Drawing.Constraints] = nil
47 Drawing.GetConstraint()
48end
49
50function GetConstraint()
51 local x = 1
52 local y = 1
53 local x2 = Drawing.Screen.Width
54 local y2 = Drawing.Screen.Height
55 for i, c in ipairs(Drawing.Constraints) do
56 if x < c[1] then
57 x = c[1]
58 end
59 if y < c[2] then
60 y = c[2]
61 end
62 if x2 > c[3] then
63 x2 = c[3]
64 end
65 if y2 > c[4] then
66 y2 = c[4]
67 end
68 end
69 Drawing.CurrentConstraint = {x, y, x2, y2}
70end
71
72function WithinContraint(x, y)
73 return Drawing.IgnoreConstraint or
74 (x >= Drawing.CurrentConstraint[1] and
75 y >= Drawing.CurrentConstraint[2] and
76 x <= Drawing.CurrentConstraint[3] and
77 y <= Drawing.CurrentConstraint[4])
78end
79
80colours.transparent = 0
81colors.transparent = 0
82
83Filters = {
84 None = {
85 [colours.white] = colours.white,
86 [colours.orange] = colours.orange,
87 [colours.magenta] = colours.magenta,
88 [colours.lightBlue] = colours.lightBlue,
89 [colours.yellow] = colours.yellow,
90 [colours.lime] = colours.lime,
91 [colours.pink] = colours.pink,
92 [colours.grey] = colours.grey,
93 [colours.lightGrey] = colours.lightGrey,
94 [colours.cyan] = colours.cyan,
95 [colours.purple] = colours.purple,
96 [colours.blue] = colours.blue,
97 [colours.brown] = colours.brown,
98 [colours.green] = colours.green,
99 [colours.red] = colours.red,
100 [colours.black] = colours.black,
101 [colours.transparent] = colours.transparent,
102 },
103
104 Greyscale = {
105 [colours.white] = colours.white,
106 [colours.orange] = colours.lightGrey,
107 [colours.magenta] = colours.lightGrey,
108 [colours.lightBlue] = colours.lightGrey,
109 [colours.yellow] = colours.lightGrey,
110 [colours.lime] = colours.lightGrey,
111 [colours.pink] = colours.lightGrey,
112 [colours.grey] = colours.grey,
113 [colours.lightGrey] = colours.lightGrey,
114 [colours.cyan] = colours.grey,
115 [colours.purple] = colours.grey,
116 [colours.blue] = colours.grey,
117 [colours.brown] = colours.grey,
118 [colours.green] = colours.grey,
119 [colours.red] = colours.grey,
120 [colours.black] = colours.black,
121 [colours.transparent] = colours.transparent,
122 },
123
124 BlackWhite = {
125 [colours.white] = colours.white,
126 [colours.orange] = colours.white,
127 [colours.magenta] = colours.white,
128 [colours.lightBlue] = colours.white,
129 [colours.yellow] = colours.white,
130 [colours.lime] = colours.white,
131 [colours.pink] = colours.white,
132 [colours.grey] = colours.black,
133 [colours.lightGrey] = colours.white,
134 [colours.cyan] = colours.black,
135 [colours.purple] = colours.black,
136 [colours.blue] = colours.black,
137 [colours.brown] = colours.black,
138 [colours.green] = colours.black,
139 [colours.red] = colours.black,
140 [colours.black] = colours.black,
141 [colours.transparent] = colours.transparent,
142 },
143
144 Darker = {
145 [colours.white] = colours.lightGrey,
146 [colours.orange] = colours.red,
147 [colours.magenta] = colours.purple,
148 [colours.lightBlue] = colours.cyan,
149 [colours.yellow] = colours.orange,
150 [colours.lime] = colours.green,
151 [colours.pink] = colours.magenta,
152 [colours.grey] = colours.black,
153 [colours.lightGrey] = colours.grey,
154 [colours.cyan] = colours.blue,
155 [colours.purple] = colours.grey,
156 [colours.blue] = colours.grey,
157 [colours.brown] = colours.grey,
158 [colours.green] = colours.grey,
159 [colours.red] = colours.brown,
160 [colours.black] = colours.black,
161 [colours.transparent] = colours.transparent,
162 },
163
164 Lighter = {
165 [colours.white] = colours.lightGrey,
166 [colours.orange] = colours.yellow,
167 [colours.magenta] = colours.pink,
168 [colours.lightBlue] = colours.cyan,
169 [colours.yellow] = colours.orange,
170 [colours.lime] = colours.green,
171 [colours.pink] = colours.magenta,
172 [colours.grey] = colours.lightGrey,
173 [colours.lightGrey] = colours.grey,
174 [colours.cyan] = colours.lightBlue,
175 [colours.purple] = colours.magenta,
176 [colours.blue] = colours.lightBlue,
177 [colours.brown] = colours.red,
178 [colours.green] = colours.lime,
179 [colours.red] = colours.orange,
180 [colours.black] = colours.grey,
181 [colours.transparent] = colours.transparent,
182 },
183
184 Highlight = {
185 [colours.white] = colours.lightGrey,
186 [colours.orange] = colours.yellow,
187 [colours.magenta] = colours.pink,
188 [colours.lightBlue] = colours.cyan,
189 [colours.yellow] = colours.orange,
190 [colours.lime] = colours.green,
191 [colours.pink] = colours.magenta,
192 [colours.grey] = colours.lightGrey,
193 [colours.lightGrey] = colours.grey,
194 [colours.cyan] = colours.lightBlue,
195 [colours.purple] = colours.magenta,
196 [colours.blue] = colours.lightBlue,
197 [colours.brown] = colours.red,
198 [colours.green] = colours.lime,
199 [colours.red] = colours.orange,
200 [colours.black] = colours.grey,
201 [colours.transparent] = colours.transparent,
202 },
203
204 Invert = {
205 [colours.white] = colours.black,
206 [colours.orange] = colours.blue,
207 [colours.magenta] = colours.green,
208 [colours.lightBlue] = colours.brown,
209 [colours.yellow] = colours.blue,
210 [colours.lime] = colours.purple,
211 [colours.pink] = colours.green,
212 [colours.grey] = colours.lightGrey,
213 [colours.lightGrey] = colours.grey,
214 [colours.cyan] = colours.red,
215 [colours.purple] = colours.green,
216 [colours.blue] = colours.yellow,
217 [colours.brown] = colours.lightBlue,
218 [colours.green] = colours.purple,
219 [colours.red] = colours.cyan,
220 [colours.black] = colours.white,
221 [colours.transparent] = colours.transparent,
222 },
223}
224
225function FilterColour(colour, filter)
226 if filter[colour] then
227 return filter[colour]
228 else
229 return colour
230 end
231end
232
233DrawCharacters = function (x, y, characters, textColour, bgColour)
234 Drawing.WriteStringToBuffer(x, y, tostring(characters), textColour, bgColour)
235end
236
237DrawBlankArea = function (x, y, w, h, colour)
238 if colour ~= colours.transparent then
239 Drawing.DrawArea (x, y, w, h, " ", 1, colour)
240 end
241end
242
243DrawArea = function (x, y, w, h, character, textColour, bgColour)
244 --width must be greater than 1, otherwise we get problems
245 if w < 0 then
246 w = w * -1
247 elseif w == 0 then
248 w = 1
249 end
250
251 for ix = 1, w do
252 local currX = x + ix - 1
253 for iy = 1, h do
254 local currY = y + iy - 1
255 Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour)
256 end
257 end
258end
259
260DrawImage = function(_x,_y,tImage, w, h)
261 if tImage then
262 for y = 1, h do
263 if not tImage[y] then
264 break
265 end
266 for x = 1, w do
267 if not tImage[y][x] then
268 break
269 end
270 local bgColour = tImage[y][x]
271 local textColour = tImage.textcol[y][x] or colours.white
272 local char = tImage.text[y][x]
273 Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour)
274 end
275 end
276 elseif w and h then
277 Drawing.DrawBlankArea(_x, _y, w, h, colours.lightGrey)
278 end
279end
280
281--using .nft
282LoadImage = function(path, global)
283 local image = {
284 text = {},
285 textcol = {}
286 }
287 if fs.exists(path) then
288 local _io = io
289 if OneOS and global then
290 _io = OneOS.IO
291 end
292 local file = _io.open(path, "r")
293 if not file then
294 error('Error Occured. _io:'..tostring(_io)..' OneOS: '..tostring(OneOS)..' OneOS.IO'..tostring(OneOS.IO)..' io: '..tostring(io))
295 end
296 local sLine = file:read()
297 local num = 1
298 while sLine do
299 table.insert(image, num, {})
300 table.insert(image.text, num, {})
301 table.insert(image.textcol, num, {})
302
303 --As we're no longer 1-1, we keep track of what index to write to
304 local writeIndex = 1
305 --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour
306 local bgNext, fgNext = false, false
307 --The current background and foreground colours
308 local currBG, currFG = nil,nil
309 for i=1,#sLine do
310 local nextChar = string.sub(sLine, i, i)
311 if nextChar:byte() == 30 then
312 bgNext = true
313 elseif nextChar:byte() == 31 then
314 fgNext = true
315 elseif bgNext then
316 currBG = Drawing.GetColour(nextChar)
317 if currBG == nil then
318 currBG = colours.transparent
319 end
320 bgNext = false
321 elseif fgNext then
322 currFG = Drawing.GetColour(nextChar)
323 if currFG == nil or currFG == colours.transparent then
324 currFG = colours.white
325 end
326 fgNext = false
327 else
328 if nextChar ~= " " and currFG == nil then
329 currFG = colours.white
330 end
331 image[num][writeIndex] = currBG
332 image.textcol[num][writeIndex] = currFG
333 image.text[num][writeIndex] = nextChar
334 writeIndex = writeIndex + 1
335 end
336 end
337 num = num+1
338 sLine = file:read()
339 end
340 file:close()
341 else
342 return nil
343 end
344 return image
345end
346
347DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour)
348 w = w or Drawing.Screen.Width
349 h = h or Drawing.Screen.Height
350 x = x or 0
351 y = y or 0
352 x = math.floor((w - #characters) / 2) + x
353 y = math.floor(h / 2) + y
354
355 Drawing.DrawCharacters(x, y, characters, textColour, bgColour)
356end
357
358GetColour = function(hex)
359 if hex == ' ' then
360 return colours.transparent
361 end
362 local value = tonumber(hex, 16)
363 if not value then return nil end
364 value = math.pow(2,value)
365 return value
366end
367
368Clear = function (_colour)
369 _colour = _colour or colours.black
370 Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour)
371end
372
373Buffer = {}
374BackBuffer = {}
375
376TryRestore = false
377
378
379--TODO: make this quicker
380-- maybe sort the pixels in order of colour so it doesn't have to set the colour each time
381DrawBuffer = function()
382 if TryRestore and Restore then
383 Restore()
384 end
385
386 for y,row in pairs(Drawing.Buffer) do
387 for x,pixel in pairs(row) do
388 local shouldDraw = true
389 local hasBackBuffer = true
390 if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then
391 hasBackBuffer = false
392 end
393 if hasBackBuffer and Drawing.BackBuffer[y][x][1] == Drawing.Buffer[y][x][1] and Drawing.BackBuffer[y][x][2] == Drawing.Buffer[y][x][2] and Drawing.BackBuffer[y][x][3] == Drawing.Buffer[y][x][3] then
394 shouldDraw = false
395 end
396 if shouldDraw then
397 term.setBackgroundColour(pixel[3])
398 term.setTextColour(pixel[2])
399 term.setCursorPos(x, y)
400 term.write(pixel[1])
401 end
402 end
403 end
404 Drawing.BackBuffer = Drawing.Buffer
405 Drawing.Buffer = {}
406end
407
408ClearBuffer = function()
409 Drawing.Buffer = {}
410end
411
412WriteStringToBuffer = function (x, y, characters, textColour,bgColour)
413 for i = 1, #characters do
414 local character = characters:sub(i,i)
415 Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour)
416 end
417end
418
419WriteToBuffer = function(x, y, character, textColour,bgColour, cached)
420 if not cached and not Drawing.WithinContraint(x, y) then
421 return
422 end
423 x = round(x)
424 y = round(y)
425
426 if textColour == colours.transparent then
427 character = ' '
428 end
429
430 if bgColour == colours.transparent then
431 Drawing.Buffer[y] = Drawing.Buffer[y] or {}
432 Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black}
433 Drawing.Buffer[y][x][1] = character
434 Drawing.Buffer[y][x][2] = textColour
435 else
436 Drawing.Buffer[y] = Drawing.Buffer[y] or {}
437 Drawing.Buffer[y][x] = {character, textColour, bgColour}
438 end
439
440 if copyBuffer then
441 copyBuffer[y] = copyBuffer[y] or {}
442 copyBuffer[y][x] = {character, textColour, bgColour}
443 end
444end
445
446DrawCachedBuffer = function(buffer)
447 for y, row in pairs(buffer) do
448 for x, pixel in pairs(row) do
449 WriteToBuffer(x, y, pixel[1], pixel[2], pixel[3], true)
450 end
451 end
452end
453
454StartCopyBuffer = function()
455 copyBuffer = {}
456end
457
458EndCopyBuffer = function()
459 local tmpCopy = copyBuffer
460 copyBuffer = nil
461 return tmpCopy
462end
463]],
464["Helpers"] = [[
465LongestString = function(input, key, isKey)
466 local length = 0
467 if isKey then
468 for k, v in pairs(input) do
469 local titleLength = string.len(k)
470 if titleLength > length then
471 length = titleLength
472 end
473 end
474 else
475 for i = 1, #input do
476 local value = input[i]
477 if key then
478 if value[key] then
479 value = value[key]
480 else
481 value = ''
482 end
483 end
484 local titleLength = string.len(value)
485 if titleLength > length then
486 length = titleLength
487 end
488 end
489 end
490 return length
491end
492
493Split = function(str,sep)
494 sep=sep or'/'
495 return str:match("(.*"..sep..")")
496end
497
498Extension = function(path, addDot)
499 if not path then
500 return nil
501 elseif not string.find(fs.getName(path), '%.') then
502 return ''
503 else
504 local _path = path
505 if path:sub(#path) == '/' then
506 _path = path:sub(1,#path-1)
507 end
508 local extension = _path:gmatch('%.[0-9a-z]+$')()
509 if extension then
510 extension = extension:sub(2)
511 else
512 --extension = nil
513 return ''
514 end
515 if addDot then
516 extension = '.'..extension
517 end
518 return extension:lower()
519 end
520end
521
522RemoveExtension = function(path)
523--local name = string.match(fs.getName(path), '(%a+)%.?.-')
524 if not path:find('%.') then
525 return path
526 end
527 local extension = Helpers.Extension(path)
528 if extension == path then
529 return fs.getName(path)
530 end
531 return string.gsub(path, extension, ''):sub(1, -2)
532end
533
534RemoveFileName = function(path)
535 if string.sub(path, -1) == '/' then
536 path = string.sub(path, 1, -2)
537 end
538 local v = string.match(path, "(.-)([^\\/]-%.?([^%.\\/]*))$")
539 if type(v) == 'string' then
540 return v
541 end
542 return v[1]
543end
544
545TruncateString = function(sString, maxLength)
546 if #sString > maxLength then
547 sString = sString:sub(1,maxLength-3)
548 if sString:sub(-1) == ' ' then
549 sString = sString:sub(1,maxLength-4)
550 end
551 sString = sString .. '...'
552 end
553 return sString
554end
555
556TruncateStringStart = function(sString, maxLength)
557 local len = #sString
558 if #sString > maxLength then
559 sString = sString:sub(len - maxLength, len - 3)
560 if sString:sub(-1) == ' ' then
561 sString = sString:sub(len - maxLength, len - 4)
562 end
563 sString = '...' .. sString
564 end
565 return sString
566end
567
568WrapText = function(text, maxWidth)
569 local lines = {''}
570 for word, space in text:gmatch('(%S+)(%s*)') do
571 local temp = lines[#lines] .. word .. space:gsub('\n','')
572 if #temp > maxWidth then
573 table.insert(lines, '')
574 end
575 if space:find('\n') then
576 lines[#lines] = lines[#lines] .. word
577
578 space = space:gsub('\n', function()
579 table.insert(lines, '')
580 return ''
581 end)
582 else
583 lines[#lines] = lines[#lines] .. word .. space
584 end
585 end
586 if #lines[1] == 0 then
587 table.remove(lines,1)
588 end
589 return lines
590end
591
592TidyPath = function(path)
593 path = '/'..path
594 if fs.exists(path) and fs.isDir(path) then
595 path = path .. '/'
596 end
597
598 path, n = path:gsub("//", "/")
599 while n > 0 do
600 path, n = path:gsub("//", "/")
601 end
602 return path
603end
604
605Capitalise = function(str)
606 return str:sub(1, 1):upper() .. str:sub(2, -1)
607end
608
609Round = function(num, idp)
610 local mult = 10^(idp or 0)
611 return math.floor(num * mult + 0.5) / mult
612end
613]],
614["Object"] = [[
615X = 1
616Y = 1
617Width = 1
618Height = 1
619Parent = nil
620OnClick = nil
621Visible = true
622IgnoreClick = false
623Name = nil
624ClipDrawing = true
625UpdateDrawBlacklist = {}
626Fixed = false
627Ready = false
628
629DrawCache = {}
630
631NeedsDraw = function(self)
632 if not self.Visible then
633 return false
634 end
635
636 if not self.DrawCache.Buffer or self.DrawCache.AlwaysDraw or self.DrawCache.NeedsDraw then
637 return true
638 end
639
640 if self.OnNeedsUpdate then
641 if self.OnNeedsUpdate() then
642 return true
643 end
644 end
645
646 if self.Children then
647 for i, v in ipairs(self.Children) do
648 if v:NeedsDraw() then
649 return true
650 end
651 end
652 end
653end
654
655GetPosition = function(self)
656 return self.Bedrock:GetAbsolutePosition(self)
657end
658
659GetOffsetPosition = function(self)
660 if not self.Parent then
661 return {X = 1, Y = 1}
662 end
663
664 local offset = {X = 0, Y = 0}
665 if not self.Fixed and self.Parent.ChildOffset then
666 offset = self.Parent.ChildOffset
667 end
668
669 return {X = self.X + offset.X, Y = self.Y + offset.Y}
670end
671
672Draw = function(self)
673 if not self.Visible then
674 return
675 end
676
677 self.DrawCache.NeedsDraw = false
678 local pos = self:GetPosition()
679 Drawing.StartCopyBuffer()
680
681 if self.ClipDrawing then
682 Drawing.AddConstraint(pos.X, pos.Y, self.Width, self.Height)
683 end
684
685 if self.OnDraw then
686 self:OnDraw(pos.X, pos.Y)
687 end
688
689 self.DrawCache.Buffer = Drawing.EndCopyBuffer()
690
691 if self.Children then
692 for i, child in ipairs(self.Children) do
693 local pos = child:GetOffsetPosition()
694 if pos.Y + self.Height > 1 and pos.Y <= self.Height and pos.X + self.Width > 1 and pos.X <= self.Width then
695 child:Draw()
696 end
697 end
698 end
699
700
701 if self.OnPostChildrenDraw then
702 self:OnPostChildrenDraw(pos.X, pos.Y)
703 end
704
705 if self.ClipDrawing then
706 Drawing.RemoveConstraint()
707 end
708end
709
710ForceDraw = function(self, ignoreChildren, ignoreParent, ignoreBedrock)
711 if not ignoreBedrock and self.Bedrock then
712 self.Bedrock:ForceDraw()
713 end
714 self.DrawCache.NeedsDraw = true
715 if not ignoreParent and self.Parent then
716 self.Parent:ForceDraw(true, nil, true)
717 end
718 if not ignoreChildren and self.Children then
719 for i, child in ipairs(self.Children) do
720 child:ForceDraw(nil, true, true)
721 end
722 end
723end
724
725OnRemove = function(self)
726 if self == self.Bedrock:GetActiveObject() then
727 self.Bedrock:SetActiveObject()
728 end
729end
730
731local function ParseColour(value)
732 if type(value) == 'string' then
733 if colours[value] and type(colours[value]) == 'number' then
734 return colours[value]
735 elseif colors[value] and type(colors[value]) == 'number' then
736 return colors[value]
737 end
738 elseif type(value) == 'number' and (value == colours.transparent or (value >= colours.white and value <= colours.black)) then
739 return value
740 end
741 error('Invalid colour: "'..tostring(value)..'"')
742end
743
744Initialise = function(self, values)
745 local _new = values -- the new instance
746 _new.DrawCache = {
747 NeedsDraw = true,
748 AlwaysDraw = false,
749 Buffer = nil
750 }
751 setmetatable(_new, {__index = self} )
752
753 local new = {} -- the proxy
754 setmetatable(new, {
755 __index = function(t, k)
756 if k:find('Color') then
757 k = k:gsub('Color', 'Colour')
758 end
759
760 if k:find('Colour') and type(_new[k]) ~= 'table' and type(_new[k]) ~= 'function' then
761 if _new[k] then
762 return ParseColour(_new[k])
763 end
764 elseif _new[k] ~= nil then
765 return _new[k]
766 end
767 end,
768
769 __newindex = function (t,k,v)
770 if k:find('Color') then
771 k = k:gsub('Color', 'Colour')
772 end
773
774 if k == 'Width' or k == 'X' or k == 'Height' or k == 'Y' then
775 v = new.Bedrock:ParseStringSize(new.Parent, k, v)
776 end
777
778 if v ~= _new[k] then
779 _new[k] = v
780 if t.OnUpdate then
781 t:OnUpdate(k)
782 end
783
784 if t.UpdateDrawBlacklist[k] == nil then
785 t:ForceDraw()
786 end
787 end
788 end
789 })
790 if new.OnInitialise then
791 new:OnInitialise()
792 end
793
794 return new
795end
796
797AnimateValue = function(self, valueName, from, to, duration, done, tbl)
798 tbl = tbl or self
799 if type(tbl[valueName]) ~= 'number' then
800 error('Animated value ('..valueName..') must be number.')
801 elseif not self.Bedrock.AnimationEnabled then
802 tbl[valueName] = to
803 if done then
804 done()
805 end
806 return
807 end
808 from = from or tbl[valueName]
809 duration = duration or 0.2
810 local delta = to - from
811
812 local startTime = os.clock()
813 local previousFrame = startTime
814 local frame
815 frame = function()
816 local time = os.clock()
817 local totalTime = time - startTime
818 local isLast = totalTime >= duration
819
820 if isLast then
821 tbl[valueName] = to
822 self:ForceDraw()
823 if done then
824 done()
825 end
826 else
827 tbl[valueName] = self.Bedrock.Helpers.Round(from + delta * (totalTime / duration))
828 self:ForceDraw()
829 self.Bedrock:StartTimer(function()
830 frame()
831 end, 0.05)
832 end
833 end
834 frame()
835end
836
837Click = function(self, event, side, x, y)
838 if self.Visible and not self.IgnoreClick then
839 if event == 'mouse_click' and self.OnClick and self:OnClick(event, side, x, y) ~= false then
840 return true
841 elseif event == 'mouse_drag' and self.OnDrag and self:OnDrag(event, side, x, y) ~= false then
842 return true
843 elseif event == 'mouse_scroll' and self.OnScroll and self:OnScroll(event, side, x, y) ~= false then
844 return true
845 else
846 return false
847 end
848 else
849 return false
850 end
851
852end
853
854ToggleMenu = function(self, name, x, y)
855 return self.Bedrock:ToggleMenu(name, self, x, y)
856end
857
858function OnUpdate(self, value)
859 if value == 'Z' then
860 self.Bedrock:ReorderObjects()
861 end
862end
863]],
864}
865local objects = {
866["Button"] = [[
867BackgroundColour = colours.lightGrey
868ActiveBackgroundColour = colours.blue
869ActiveTextColour = colours.white
870TextColour = colours.black
871DisabledTextColour = colours.lightGrey
872Text = ""
873Toggle = nil
874Momentary = true
875AutoWidth = true
876Align = 'Center'
877Enabled = true
878
879OnUpdate = function(self, value)
880 if value == 'Text' and self.AutoWidth then
881 self.Width = #self.Text + 2
882 end
883end
884
885OnDraw = function(self, x, y)
886 local bg = self.BackgroundColour
887
888 if self.Toggle then
889 bg = self.ActiveBackgroundColour
890 end
891
892 local txt = self.TextColour
893 if self.Toggle then
894 txt = self.ActiveTextColour
895 end
896 if not self.Enabled then
897 txt = self.DisabledTextColour
898 end
899 Drawing.DrawBlankArea(x, y, self.Width, self.Height, bg)
900
901 local _x = 1
902 if self.Align == 'Right' then
903 _x = self.Width - #self.Text - 1
904 elseif self.Align == 'Center' then
905 _x = math.floor((self.Width - #self.Text) / 2)
906 end
907
908 Drawing.DrawCharacters(x + _x, y-1+math.ceil(self.Height/2), self.Text, txt, bg)
909end
910
911OnLoad = function(self)
912 if self.Toggle ~= nil then
913 self.Momentary = false
914 end
915end
916
917Click = function(self, event, side, x, y)
918 if self.Visible and not self.IgnoreClick and self.Enabled and event ~= 'mouse_scroll' then
919 if self.OnClick then
920 if self.Momentary then
921 self.Toggle = true
922 self.Bedrock:StartTimer(function()self.Toggle = false end,0.25)
923 elseif self.Toggle ~= nil then
924 self.Toggle = not self.Toggle
925 end
926
927 self:OnClick(event, side, x, y, self.Toggle)
928 else
929 self.Toggle = not self.Toggle
930 end
931 return true
932 else
933 return false
934 end
935end
936]],
937["CollectionView"] = [[
938Inherit = 'ScrollView'
939UpdateDrawBlacklist = {['NeedsItemUpdate']=true}
940
941TextColour = colours.black
942BackgroundColour = colours.white
943Items = false
944NeedsItemUpdate = false
945SpacingX = 2
946SpacingY = 1
947
948OnDraw = function(self, x, y)
949 if self.NeedsItemUpdate then
950 self:UpdateItems()
951 self.NeedsItemUpdate = false
952 end
953 Drawing.DrawBlankArea(x, y, self.Width, self.Height, self.BackgroundColour)
954end
955
956local function MaxIcons(self, obj)
957 local x, y = 2, 1
958 if not obj.Height or not obj.Width then
959 error('You must provide each object\'s height when adding to a CollectionView.')
960 end
961 local slotHeight = obj.Height + self.SpacingY
962 local slotWidth = obj.Width + self.SpacingX
963 local maxX = math.floor((self.Width - 2) / slotWidth)
964 return x, y, maxX, slotWidth, slotHeight
965end
966
967local function IconLocation(self, obj, i)
968 local x, y, maxX, slotWidth, slotHeight = MaxIcons(self, obj)
969 local rowPos = ((i - 1) % maxX)
970 local colPos = math.ceil(i / maxX) - 1
971 x = x + (slotWidth * rowPos)
972 y = y + colPos * slotHeight
973 return x, y
974end
975
976local function AddItem(self, v, i)
977 local toggle = false
978 if not self.CanSelect then
979 toggle = nil
980 end
981 local x, y = IconLocation(self, v, i)
982 local item = {
983 ["X"]=x,
984 ["Y"]=y,
985 ["Name"]="CollectionViewItem",
986 ["Type"]="View",
987 ["TextColour"]=self.TextColour,
988 ["BackgroundColour"]=0,
989 OnClick = function(itm)
990 if self.CanSelect then
991 for i2, _v in ipairs(self.Children) do
992 _v.Toggle = false
993 end
994 self.Selected = itm
995 end
996 end
997 }
998 for k, _v in pairs(v) do
999 item[k] = _v
1000 end
1001 self:AddObject(item)
1002end
1003
1004
1005UpdateItems = function(self)
1006 self:RemoveAllObjects()
1007 local groupMode = false
1008 for k, v in pairs(self.Items) do
1009 if type(k) == 'string' then
1010 groupMode = true
1011 break
1012 end
1013 end
1014
1015 for i, v in ipairs(self.Items) do
1016 AddItem(self, v, i)
1017 end
1018 self:UpdateScroll()
1019end
1020
1021OnUpdate = function(self, value)
1022 if value == 'Items' then
1023 self.NeedsItemUpdate = true
1024 end
1025end
1026]],
1027["ImageView"] = [[
1028Image = false
1029
1030OnDraw = function(self, x, y)
1031 Drawing.DrawImage(x, y, self.Image, self.Width, self.Height)
1032end
1033
1034OnLoad = function(self)
1035 if self.Path and fs.exists(self.Path) then
1036 self.Image = Drawing.LoadImage(self.Path)
1037 end
1038end
1039
1040OnUpdate = function(self, value)
1041 if value == 'Path' then
1042 if self.Path and fs.exists(self.Path) then
1043 self.Image = Drawing.LoadImage(self.Path)
1044 end
1045 end
1046end
1047]],
1048["Label"] = [[
1049TextColour = colours.black
1050BackgroundColour = colours.transparent
1051Text = ""
1052AutoWidth = false
1053Align = 'Left'
1054
1055local wrapText = function(text, maxWidth)
1056 local lines = {''}
1057 for word, space in text:gmatch('(%S+)(%s*)') do
1058 local temp = lines[#lines] .. word .. space:gsub('\n','')
1059 if #temp > maxWidth then
1060 table.insert(lines, '')
1061 end
1062 if space:find('\n') then
1063 lines[#lines] = lines[#lines] .. word
1064
1065 space = space:gsub('\n', function()
1066 table.insert(lines, '')
1067 return ''
1068 end)
1069 else
1070 lines[#lines] = lines[#lines] .. word .. space
1071 end
1072 end
1073 if #lines[1] == 0 then
1074 table.remove(lines,1)
1075 end
1076 return lines
1077end
1078
1079OnUpdate = function(self, value)
1080 if value == 'Text' then
1081 if self.AutoWidth then
1082 self.Width = #self.Text
1083 else
1084 self.Height = #wrapText(self.Text, self.Width)
1085 end
1086 end
1087end
1088
1089OnDraw = function(self, x, y)
1090 for i, v in ipairs(wrapText(self.Text, self.Width)) do
1091 local _x = 0
1092 if self.Align == 'Right' then
1093 _x = self.Width - #v
1094 elseif self.Align == 'Center' then
1095 _x = math.floor((self.Width - #v) / 2)
1096 end
1097 Drawing.DrawCharacters(x + _x, y + i - 1, v, self.TextColour, self.BackgroundColour)
1098 end
1099end
1100]],
1101["ListView"] = [[
1102Inherit = 'ScrollView'
1103UpdateDrawBlacklist = {['NeedsItemUpdate']=true}
1104
1105TextColour = colours.black
1106BackgroundColour = colours.white
1107HeadingColour = colours.lightGrey
1108SelectionBackgroundColour = colours.blue
1109SelectionTextColour = colours.white
1110Items = false
1111CanSelect = false
1112Selected = nil
1113NeedsItemUpdate = false
1114ItemMargin = 1
1115HeadingMargin = 0
1116TopMargin = 0
1117
1118OnDraw = function(self, x, y)
1119 if self.NeedsItemUpdate then
1120 self:UpdateItems()
1121 end
1122 Drawing.DrawBlankArea(x, y, self.Width, self.Height, self.BackgroundColour)
1123end
1124
1125local function AddItem(self, v, x, y, group)
1126 local toggle = false
1127 if not self.CanSelect then
1128 toggle = nil
1129 elseif v.Selected then
1130 toggle = true
1131 end
1132 local item = {
1133 ["Width"]=self.Width,
1134 ["X"]=x,
1135 ["Y"]=y,
1136 ["Name"]="ListViewItem",
1137 ["Type"]="Button",
1138 ["TextColour"]=self.TextColour,
1139 ["BackgroundColour"]=0,
1140 ["ActiveTextColour"]=self.SelectionTextColour,
1141 ["ActiveBackgroundColour"]=self.SelectionBackgroundColour,
1142 ["Align"]='Left',
1143 ["Toggle"]=toggle,
1144 ["Group"]=group,
1145 OnClick = function(itm)
1146 if self.CanSelect then
1147 self:SelectItem(itm)
1148 elseif self.OnSelect then
1149 self:OnSelect(itm.Text)
1150 end
1151 end
1152 }
1153 if type(v) == 'table' then
1154 for k, _v in pairs(v) do
1155 item[k] = _v
1156 end
1157 else
1158 item.Text = v
1159 end
1160
1161 local itm = self:AddObject(item)
1162 if v.Selected then
1163 self:SelectItem(itm)
1164 end
1165end
1166
1167UpdateItems = function(self)
1168 if not self.Items or type(self.Items) ~= 'table' then
1169 self.Items = {}
1170 end
1171 self.Selected = nil
1172 self:RemoveAllObjects()
1173 local groupMode = false
1174 for k, v in pairs(self.Items) do
1175 if type(k) == 'string' then
1176 groupMode = true
1177 break
1178 end
1179 end
1180
1181 if not groupMode then
1182 for i, v in ipairs(self.Items) do
1183 AddItem(self, v, self.ItemMargin, i)
1184 end
1185 else
1186 local y = self.TopMargin
1187 for k, v in pairs(self.Items) do
1188 y = y + 1
1189 AddItem(self, {Text = k, TextColour = self.HeadingColour, IgnoreClick = true}, self.HeadingMargin, y)
1190 for i, _v in ipairs(v) do
1191 y = y + 1
1192 AddItem(self, _v, 1, y, k)
1193 end
1194 y = y + 1
1195 end
1196 end
1197 self:UpdateScroll()
1198 self.NeedsItemUpdate = false
1199end
1200
1201OnKeyChar = function(self, event, keychar)
1202 if keychar == keys.up or keychar == keys.down then
1203 local n = self:GetIndex(self.Selected)
1204 if keychar == keys.up then
1205 n = n - 1
1206 else
1207 n = n + 1
1208 end
1209 local new = self:GetNth(n)
1210 if new then
1211 self:SelectItem(new)
1212 end
1213 elseif keychar == keys.enter and self.Selected then
1214 self.Selected:Click('mouse_click', 1, 1, 1)
1215 end
1216end
1217
1218--returns the index/'n' of the given item
1219GetIndex = function(self, obj)
1220 local n = 1
1221 for i, v in ipairs(self.Children) do
1222 if not v.IgnoreClick then
1223 if obj == v then
1224 return n
1225 end
1226 n = n + 1
1227 end
1228 end
1229end
1230
1231--gets the 'nth' list item (does not include headings)
1232GetNth = function(self, n)
1233 local _n = 1
1234 for i, v in ipairs(self.Children) do
1235 if not v.IgnoreClick then
1236 if n == _n then
1237 return v
1238 end
1239 _n = _n + 1
1240 end
1241 end
1242end
1243
1244SelectItem = function(self, item)
1245 for i, v in ipairs(self.Children) do
1246 v.Toggle = false
1247 end
1248 self.Selected = item
1249 item.Toggle = true
1250 if self.OnSelect then
1251 self:OnSelect(item.Text)
1252 end
1253end
1254
1255OnUpdate = function(self, value)
1256 if value == 'Items' then
1257 self.NeedsItemUpdate = true
1258 end
1259end
1260]],
1261["Menu"] = [[
1262Inherit = 'View'
1263
1264TextColour = colours.black
1265BackgroundColour = colours.white
1266HideTop = false
1267Prepared = false
1268
1269OnDraw = function(self, x, y)
1270 Drawing.IgnoreConstraint = true
1271 Drawing.DrawBlankArea(x + 1, y + (self.HideTop and 0 or 1), self.Width, self.Height + (self.HideTop and 1 or 0), colours.grey)
1272 Drawing.IgnoreConstraint = false
1273 Drawing.DrawBlankArea(x, y, self.Width, self.Height, self.BackgroundColour)
1274end
1275
1276OnLoad = function(self)
1277 local owner = self.Owner
1278 if type(owner) == 'string' then
1279 owner = self.Bedrock:GetObject(self.Owner)
1280 end
1281
1282 if owner then
1283 if self.X == 0 and self.Y == 0 then
1284 local pos = owner:GetPosition()
1285 self.X = pos.X
1286 self.Y = pos.Y + owner.Height
1287 end
1288 self.Owner = owner
1289 else
1290 self.Owner = nil
1291 end
1292end
1293
1294OnUpdate = function(self, value)
1295 if value == 'Children' then
1296 self.Height = #self.Children + 1 + (self.HideTop and 0 or 1)
1297 if not self.BaseY then
1298 self.BaseY = self.Y
1299 end
1300 if #self.Children > 0 and self.Children[1].Type == 'Button' then
1301 self.Width = self.Bedrock.Helpers.LongestString(self.Children, 'Text') + 2
1302 for i, v in ipairs(self.Children) do
1303 if v.TextColour then
1304 v.TextColour = self.TextColour
1305 end
1306 if v.BackgroundColour then
1307 v.BackgroundColour = colours.transparent
1308 end
1309 if v.Colour then
1310 v.Colour = colours.lightGrey
1311 end
1312 v.Align = 'Left'
1313 v.X = 1
1314 v.Y = i + (self.HideTop and 0 or 1)
1315 v.Width = self.Width
1316 v.Height = 1
1317 end
1318 elseif #self.Children > 0 and self.Children[1].Type == 'MenuItem' then
1319 local width = 1
1320 for i, v in ipairs(self.Children) do
1321 if v.Width > width then
1322 width = v.Width
1323 end
1324 end
1325 self.Width = width
1326 for i, v in ipairs(self.Children) do
1327 if v.TextColour then
1328 v.TextColour = self.TextColour
1329 end
1330 if v.Colour then
1331 v.Colour = colours.lightGrey
1332 end
1333 v.X = 1
1334 v.Y = i + (self.HideTop and 0 or 1)
1335 v.Width = width
1336 v.Height = 1
1337 end
1338 end
1339
1340 self.Y = self.BaseY
1341 local pos = self:GetPosition()
1342 if pos.Y + self.Height + 1 > Drawing.Screen.Height then
1343 self.Y = self.BaseY - ((self.Height + pos.Y) - Drawing.Screen.Height)
1344 end
1345
1346 if pos.X + self.Width > Drawing.Screen.Width then
1347 self.X = Drawing.Screen.Width - self.Width
1348 end
1349 end
1350end
1351
1352Close = function(self, isBedrockCall)
1353 self.Bedrock.Menu = nil
1354 if not self.Prepared then
1355 self.Parent:RemoveObject(self)
1356 else
1357 self.Visible = false
1358 end
1359
1360 if self.Owner and self.Owner.Toggle then
1361 self.Owner.Toggle = false
1362 end
1363 self.Parent:ForceDraw()
1364 self = nil
1365end
1366
1367OnChildClick = function(self, child, event, side, x, y)
1368 self:Close()
1369end
1370]],
1371["MenuItem"] = [[
1372BackgroundColour = colours.white
1373TextColour = colours.black
1374DisabledTextColour = colours.lightGrey
1375ShortcutTextColour = colours.grey
1376Text = ""
1377Enabled = true
1378Shortcut = nil
1379ShortcutPadding = 2
1380ShortcutName = nil
1381
1382OnUpdate = function(self, value)
1383 if value == 'Text' then
1384 if self.Shortcut then
1385 self.Width = #self.Text + 2 + self.ShortcutPadding + #self.Shortcut
1386 else
1387 self.Width = #self.Text + 2
1388 end
1389 elseif value == 'OnClick' then
1390 self:RegisterShortcut()
1391 end
1392end
1393
1394OnDraw = function(self, x, y)
1395 Drawing.DrawBlankArea(x, y, self.Width, self.Height, self.BackgroundColour)
1396
1397 local txt = self.TextColour
1398 if not self.Enabled then
1399 txt = self.DisabledTextColour
1400 end
1401 Drawing.DrawCharacters(x + 1, y, self.Text, txt, colours.transparent)
1402
1403 if self.Shortcut then
1404 local shrt = self.ShortcutTextColour
1405 if not self.Enabled then
1406 shrt = self.DisabledTextColour
1407 end
1408 Drawing.DrawCharacters(x + self.Width - #self.Shortcut - 1, y, self.Shortcut, shrt, colours.transparent)
1409 end
1410end
1411
1412ParseShortcut = function(self)
1413 local special = {
1414 ['^'] = keys.leftShift,
1415 ['<'] = keys.delete,
1416 ['>'] = keys.delete,
1417 ['#'] = keys.leftCtrl,
1418 ['~'] = keys.leftAlt,
1419 }
1420
1421 local keys = {}
1422 for i = 1, #self.Shortcut do
1423 local c = self.Shortcut:sub(i,i)
1424 table.insert(keys, special[c] or c:lower())
1425 end
1426 return keys
1427end
1428
1429RegisterShortcut = function(self)
1430 if self.Shortcut then
1431 self.Shortcut = self.Shortcut:upper()
1432 self.ShortcutName = self.Bedrock:RegisterKeyboardShortcut(self:ParseShortcut(), function()
1433 if self.OnClick and self.Enabled then
1434 if self.Parent.Owner then
1435 self.Parent:Close()
1436 self.Parent.Owner.Toggle = true
1437 self.Bedrock:StartTimer(function()
1438 self.Parent.Owner.Toggle = false
1439 end, 0.3)
1440 end
1441 return self:OnClick('keyboard_shortcut', 1, 1, 1)
1442 else
1443 return false
1444 end
1445 end)
1446 end
1447end
1448
1449OnRemove = function(self)
1450 if self.ShortcutName then
1451 self.Bedrock:UnregisterKeyboardShortcut(self.ShortcutName)
1452 end
1453end
1454
1455OnLoad = function(self)
1456 if self.OnClick ~= nil then
1457 self:RegisterShortcut()
1458 end
1459 -- self:OnUpdate('Text')
1460end
1461]],
1462["NumberBox"] = [[
1463Inherit = 'View'
1464
1465Value = 1
1466Minimum = 1
1467Maximum = 99
1468BackgroundColour = colours.lightGrey
1469TextBoxTimer = nil
1470Width = 7
1471
1472OnLoad = function(self)
1473 self:AddObject({
1474 X = self.Width - 1,
1475 Y = 1,
1476 Width = 1,
1477 AutoWidth = false,
1478 Text = '-',
1479 Type = 'Button',
1480 Name = 'AddButton',
1481 BackgroundColour = colours.transparent,
1482 OnClick = function()
1483 self:ShiftValue(-1)
1484 end
1485 })
1486
1487 self:AddObject({
1488 X = self.Width,
1489 Y = 1,
1490 Width = 1,
1491 AutoWidth = false,
1492 Text = '+',
1493 Type = 'Button',
1494 Name = 'SubButton',
1495 BackgroundColour = colours.transparent,
1496 OnClick = function()
1497 self:ShiftValue(1)
1498 end
1499 })
1500
1501 self:AddObject({
1502 X = 1,
1503 Y = 1,
1504 Width = self.Width - 2,
1505 Text = tostring(self.Value),
1506 Align = 'Center',
1507 Type = 'TextBox',
1508 BackgroundColour = colours.transparent,
1509 OnChange = function(_self, event, keychar)
1510 if keychar == keys.enter then
1511 self:SetValue(tonumber(_self.Text))
1512 self.TextBoxTimer = nil
1513 end
1514 if self.TextBoxTimer then
1515 self.Bedrock:StopTimer(self.TextBoxTimer)
1516 end
1517
1518 self.TextBoxTimer = self.Bedrock:StartTimer(function(_, timer)
1519 if timer and timer == self.TextBoxTimer then
1520 self:SetValue(tonumber(_self.Text))
1521 self.TextBoxTimer = nil
1522 end
1523 end, 2)
1524 end
1525 })
1526end
1527
1528OnScroll = function(self, event, dir, x, y)
1529 self:ShiftValue(-dir)
1530end
1531
1532ShiftValue = function(self, delta)
1533 local val = tonumber(self:GetObject('TextBox').Text) or self.Minimum
1534 self:SetValue(val + delta)
1535end
1536
1537SetValue = function(self, newValue)
1538 newValue = newValue or 0
1539 if self.Maximum and newValue > self.Maximum then
1540 newValue = self.Maximum
1541 elseif self.Minimum and newValue < self.Minimum then
1542 newValue = self.Minimum
1543 end
1544 self.Value = newValue
1545 if self.OnChange then
1546 self:OnChange()
1547 end
1548end
1549
1550OnUpdate = function(self, value)
1551 if value == 'Value' then
1552 local textbox = self:GetObject('TextBox')
1553 if textbox then
1554 textbox.Text = tostring(self.Value)
1555 end
1556 end
1557end
1558]],
1559["ProgressBar"] = [[
1560BackgroundColour = colours.lightGrey
1561BarColour = colours.blue
1562TextColour = colours.white
1563ShowText = false
1564Value = 0
1565Maximum = 1
1566Indeterminate = false
1567AnimationStep = 0
1568
1569OnDraw = function(self, x, y)
1570 Drawing.DrawBlankArea(x, y, self.Width, self.Height, self.BackgroundColour)
1571
1572 -- if self.Indeterminate then
1573 -- for i = 1, self.Width do
1574 -- local s = x + i - 1 + self.AnimationStep
1575 -- if s % 4 == 1 or s % 4 == 2 then
1576 -- Drawing.DrawBlankArea(s, y, 1, self.Height, self.BarColour)
1577 -- end
1578 -- end
1579 -- self.AnimationStep = self.AnimationStep + 1
1580 -- if self.AnimationStep >= 4 then
1581 -- self.AnimationStep = 0
1582 -- end
1583 -- self.Bedrock:StartTimer(function()
1584 -- self:Draw()
1585 -- end, 0.25)
1586 -- else
1587 local values = self.Value
1588 local barColours = self.BarColour
1589 if type(values) == 'number' then
1590 values = {values}
1591 end
1592 if type(barColours) == 'number' then
1593 barColours = {barColours}
1594 end
1595 local total = 0
1596 local _x = x
1597 for i, v in ipairs(values) do
1598 local width = self.Bedrock.Helpers.Round((v / self.Maximum) * self.Width)
1599 total = total + v
1600 Drawing.DrawBlankArea(_x, y, width, self.Height, barColours[((i-1)%#barColours)+1])
1601 _x = _x + width
1602 end
1603
1604 if self.ShowText then
1605 local text = self.Bedrock.Helpers.Round((total / self.Maximum) * 100) .. '%'
1606 Drawing.DrawCharactersCenter(x, y, self.Width, self.Height, text, self.TextColour, colours.transparent)
1607 end
1608 -- end
1609end
1610]],
1611["ScrollBar"] = [[
1612BackgroundColour = colours.lightGrey
1613BarColour = colours.lightBlue
1614Scroll = 0
1615MaxScroll = 0
1616ClickPoint = nil
1617Fixed = true
1618
1619OnUpdate = function(self, value)
1620 if value == 'Text' and self.AutoWidth then
1621 self.Width = #self.Text + 2
1622 end
1623end
1624
1625OnDraw = function(self, x, y)
1626 local barHeight = self.Height * (self.Height / (self.Height + self.MaxScroll))
1627 if barHeight < 3 then
1628 barHeight = 3
1629 end
1630 local percentage = (self.Scroll/self.MaxScroll)
1631
1632 Drawing.DrawBlankArea(x, y, self.Width, self.Height, self.BackgroundColour)
1633 Drawing.DrawBlankArea(x, y + math.ceil(self.Height*percentage - barHeight*percentage), self.Width, barHeight, self.BarColour)
1634end
1635
1636OnScroll = function(self, event, direction, x, y)
1637 if event == 'mouse_scroll' then
1638 direction = self.Bedrock.Helpers.Round(direction * 3)
1639 end
1640 if self.Scroll < 0 or self.Scroll > self.MaxScroll then
1641 return false
1642 end
1643 local old = self.Scroll
1644 self.Scroll = self.Bedrock.Helpers.Round(self.Scroll + direction)
1645 if self.Scroll < 0 then
1646 self.Scroll = 0
1647 elseif self.Scroll > self.MaxScroll then
1648 self.Scroll = self.MaxScroll
1649 end
1650
1651 if self.Scroll ~= old and self.OnChange then
1652 self:OnChange()
1653 end
1654end
1655
1656OnClick = function(self, event, side, x, y)
1657 if event == 'mouse_click' then
1658 self.ClickPoint = y
1659 else
1660 if self.ClickPoint then
1661 local gapHeight = self.Height - (self.Height * (self.Height / (self.Height + self.MaxScroll)))
1662 local barHeight = self.Height * (self.Height / (self.Height + self.MaxScroll))
1663 --local delta = (self.Height + self.MaxScroll) * ((y - self.ClickPoint) / barHeight)
1664 local delta = ((y - self.ClickPoint)/gapHeight)*self.MaxScroll
1665 --l(((y - self.ClickPoint)/gapHeight))
1666 --l(delta)
1667 self.Scroll = self.Bedrock.Helpers.Round(delta)
1668 --l(self.Scroll)
1669 --l('----')
1670 if self.Scroll < 0 then
1671 self.Scroll = 0
1672 elseif self.Scroll > self.MaxScroll then
1673 self.Scroll = self.MaxScroll
1674 end
1675 if self.OnChange then
1676 self:OnChange()
1677 end
1678 end
1679 end
1680
1681 local relScroll = self.MaxScroll * ((y-1)/self.Height)
1682 if y == self.Height then
1683 relScroll = self.MaxScroll
1684 end
1685 self.Scroll = self.Bedrock.Helpers.Round(relScroll)
1686
1687
1688end
1689
1690OnDrag = OnClick
1691]],
1692["ScrollView"] = [[
1693Inherit = 'View'
1694ChildOffset = false
1695ContentWidth = 0
1696ContentHeight = 0
1697ScrollBarBackgroundColour = colours.lightGrey
1698ScrollBarColour = colours.lightBlue
1699
1700CalculateContentSize = function(self)
1701 local function calculateObject(obj)
1702 local pos = obj:GetPosition()
1703 local x2 = pos.X + obj.Width - 1
1704 local y2 = pos.Y + obj.Height - 1
1705 if obj.Children then
1706 for i, child in ipairs(obj.Children) do
1707 local _x2, _y2 = calculateObject(child)
1708 if _x2 > x2 then
1709 x2 = _x2
1710 end
1711 if _y2 > y2 then
1712 y2 = _y2
1713 end
1714 end
1715 end
1716 return x2, y2
1717 end
1718
1719 local pos = self:GetPosition()
1720 local x2, y2 = calculateObject(self)
1721 self.ContentWidth = x2 - pos.X + 1
1722 self.ContentHeight = y2 - pos.Y + 1
1723end
1724
1725UpdateScroll = function(self)
1726 self.ChildOffset.Y = 0
1727 self:CalculateContentSize()
1728 if self.ContentHeight > self.Height then
1729 if not self:GetObject('ScrollViewScrollBar') then
1730 local _scrollBar = self:AddObject({
1731 ["Name"] = 'ScrollViewScrollBar',
1732 ["Type"] = 'ScrollBar',
1733 ["X"] = self.Width,
1734 ["Y"] = 1,
1735 ["Width"] = 1,
1736 ["Height"] = self.Height,
1737 ["BackgroundColour"] = self.ScrollBarBackgroundColour,
1738 ["BarColour"] = self.ScrollBarColour,
1739 ["Z"]=999
1740 })
1741
1742 _scrollBar.OnChange = function(scrollBar)
1743 self.ChildOffset.Y = -scrollBar.Scroll
1744 for i, child in ipairs(self.Children) do
1745 child:ForceDraw()
1746 end
1747 end
1748 end
1749
1750 if self:GetObject('ScrollViewScrollBar') then
1751 self:GetObject('ScrollViewScrollBar').MaxScroll = self.ContentHeight - self.Height
1752 end
1753 else
1754 self:RemoveObject('ScrollViewScrollBar')
1755 end
1756end
1757
1758OnScroll = function(self, event, direction, x, y)
1759 if self:GetObject('ScrollViewScrollBar') then
1760 self:GetObject('ScrollViewScrollBar'):OnScroll(event, direction, x, y)
1761 end
1762end
1763
1764OnLoad = function(self)
1765 if not self.ChildOffset or not self.ChildOffset.X or not self.ChildOffset.Y then
1766 self.ChildOffset = {X = 0, Y = 0}
1767 end
1768 self:UpdateScroll()
1769end
1770]],
1771["SecureTextBox"] = [[
1772Inherit = 'TextBox'
1773MaskCharacter = '*'
1774
1775OnDraw = function(self, x, y)
1776 Drawing.DrawBlankArea(x, y, self.Width, self.Height, self.BackgroundColour)
1777 if self.CursorPos > #self.Text then
1778 self.CursorPos = #self.Text
1779 elseif self.CursorPos < 0 then
1780 self.CursorPos = 0
1781 end
1782 local text = ''
1783
1784 for i = 1, #self.Text do
1785 text = text .. self.MaskCharacter
1786 end
1787
1788 if self.Bedrock:GetActiveObject() == self then
1789 if #text > (self.Width - 2) then
1790 text = text:sub(#text-(self.Width - 3))
1791 self.Bedrock.CursorPos = {x + 1 + self.Width-2, y}
1792 else
1793 self.Bedrock.CursorPos = {x + 1 + self.CursorPos, y}
1794 end
1795 self.Bedrock.CursorColour = self.TextColour
1796 end
1797
1798 if #tostring(text) == 0 then
1799 Drawing.DrawCharacters(x + 1, y, self.Placeholder, self.PlaceholderTextColour, self.BackgroundColour)
1800 else
1801 if not self.Selected then
1802 Drawing.DrawCharacters(x + 1, y, text, self.TextColour, self.BackgroundColour)
1803 else
1804 for i = 1, #text do
1805 local char = text:sub(i, i)
1806 local textColour = self.TextColour
1807 local backgroundColour = self.BackgroundColour
1808 if i > self.DragStart and i - 1 <= self.CursorPos then
1809 textColour = self.SelectedTextColour
1810 backgroundColour = self.SelectedBackgroundColour
1811 end
1812 Drawing.DrawCharacters(x + i, y, char, textColour, backgroundColour)
1813 end
1814 end
1815 end
1816end
1817]],
1818["Separator"] = [[
1819Colour = colours.grey
1820
1821OnDraw = function(self, x, y)
1822 local char = "|"
1823 if self.Width > self.Height then
1824 char = '-'
1825 end
1826 Drawing.DrawArea(x, y, self.Width, self.Height, char, self.Colour, colours.transparent)
1827end
1828]],
1829["TextBox"] = [[
1830BackgroundColour = colours.lightGrey
1831SelectedBackgroundColour = colours.blue
1832SelectedTextColour = colours.white
1833TextColour = colours.black
1834PlaceholderTextColour = colours.grey
1835Placeholder = ''
1836AutoWidth = false
1837Text = ""
1838CursorPos = nil
1839Numerical = false
1840DragStart = nil
1841Selected = false
1842SelectOnClick = false
1843ActualDragStart = nil
1844
1845OnDraw = function(self, x, y)
1846 Drawing.DrawBlankArea(x, y, self.Width, self.Height, self.BackgroundColour)
1847 if self.CursorPos > #self.Text then
1848 self.CursorPos = #self.Text
1849 elseif self.CursorPos < 0 then
1850 self.CursorPos = 0
1851 end
1852 local text = self.Text
1853 local offset = self:TextOffset()
1854 if #text > (self.Width - 2) then
1855 text = text:sub(offset+1, offset + self.Width - 2)
1856 -- self.Bedrock.CursorPos = {x + 1 + self.Width-2, y}
1857 -- else
1858 end
1859 if self.Bedrock:GetActiveObject() == self then
1860 self.Bedrock.CursorPos = {x + 1 + self.CursorPos - offset, y}
1861 self.Bedrock.CursorColour = self.TextColour
1862 else
1863 self.Selected = false
1864 end
1865
1866 if #tostring(text) == 0 then
1867 Drawing.DrawCharacters(x + 1, y, self.Placeholder, self.PlaceholderTextColour, self.BackgroundColour)
1868 else
1869 if not self.Selected then
1870 Drawing.DrawCharacters(x + 1, y, text, self.TextColour, self.BackgroundColour)
1871 else
1872 local startPos = self.DragStart - offset
1873 local endPos = self.CursorPos - offset
1874 if startPos > endPos then
1875 startPos = self.CursorPos - offset
1876 endPos = self.DragStart - offset
1877 end
1878 for i = 1, #text do
1879 local char = text:sub(i, i)
1880 local textColour = self.TextColour
1881 local backgroundColour = self.BackgroundColour
1882
1883 if i > startPos and i - 1 <= endPos then
1884 textColour = self.SelectedTextColour
1885 backgroundColour = self.SelectedBackgroundColour
1886 end
1887 Drawing.DrawCharacters(x + i, y, char, textColour, backgroundColour)
1888 end
1889 end
1890 end
1891end
1892
1893TextOffset = function(self)
1894 if #self.Text < (self.Width - 2) then
1895 return 0
1896 elseif self.Bedrock:GetActiveObject() ~= self then
1897 return 0
1898 else
1899 local textWidth = (self.Width - 2)
1900 local offset = self.CursorPos - textWidth
1901 if offset < 0 then
1902 offset = 0
1903 end
1904 return offset
1905 end
1906end
1907
1908OnLoad = function(self)
1909 if not self.CursorPos then
1910 self.CursorPos = #self.Text
1911 end
1912end
1913
1914OnClick = function(self, event, side, x, y)
1915 if self.Bedrock:GetActiveObject() ~= self and self.SelectOnClick then
1916 self.CursorPos = #self.Text - 1
1917 self.DragStart = 0
1918 self.ActualDragStart = x - 2 + self:TextOffset()
1919 self.Selected = true
1920 else
1921 self.CursorPos = x - 2 + self:TextOffset()
1922 self.DragStart = self.CursorPos
1923 self.Selected = false
1924 end
1925 self.Bedrock:SetActiveObject(self)
1926end
1927
1928OnDrag = function(self, event, side, x, y)
1929 self.CursorPos = x - 2 + self:TextOffset()
1930 if self.ActualDragStart then
1931 self.DragStart = self.ActualDragStart
1932 self.ActualDragStart = nil
1933 end
1934 if self.DragStart then
1935 self.Selected = true
1936 end
1937end
1938
1939OnKeyChar = function(self, event, keychar)
1940 local deleteSelected = function()
1941 if self.Selected then
1942 local startPos = self.DragStart
1943 local endPos = self.CursorPos
1944 if startPos > endPos then
1945 startPos = self.CursorPos
1946 endPos = self.DragStart
1947 end
1948 self.Text = self.Text:sub(1, startPos) .. self.Text:sub(endPos + 2)
1949 self.CursorPos = startPos
1950 self.DragStart = nil
1951 self.Selected = false
1952 return true
1953 end
1954 end
1955
1956 if event == 'char' then
1957 deleteSelected()
1958 if self.Numerical then
1959 keychar = tostring(tonumber(keychar))
1960 end
1961 if keychar == 'nil' then
1962 return
1963 end
1964 self.Text = string.sub(self.Text, 1, self.CursorPos ) .. keychar .. string.sub( self.Text, self.CursorPos + 1 )
1965 if self.Numerical then
1966 self.Text = tostring(tonumber(self.Text))
1967 if self.Text == 'nil' then
1968 self.Text = '1'
1969 end
1970 end
1971
1972 self.CursorPos = self.CursorPos + 1
1973 if self.OnChange then
1974 self:OnChange(event, keychar)
1975 end
1976 return false
1977 elseif event == 'key' then
1978 if keychar == keys.enter then
1979 if self.OnChange then
1980 self:OnChange(event, keychar)
1981 end
1982 elseif keychar == keys.left then
1983 -- Left
1984 if self.CursorPos > 0 then
1985 if self.Selected then
1986 self.CursorPos = self.DragStart
1987 self.DragStart = nil
1988 self.Selected = false
1989 else
1990 self.CursorPos = self.CursorPos - 1
1991 end
1992 if self.OnChange then
1993 self:OnChange(event, keychar)
1994 end
1995 end
1996
1997 elseif keychar == keys.right then
1998 -- Right
1999 if self.CursorPos < string.len(self.Text) then
2000 if self.Selected then
2001 self.CursorPos = self.CursorPos
2002 self.DragStart = nil
2003 self.Selected = false
2004 else
2005 self.CursorPos = self.CursorPos + 1
2006 end
2007 if self.OnChange then
2008 self:OnChange(event, keychar)
2009 end
2010 end
2011
2012 elseif keychar == keys.backspace then
2013 -- Backspace
2014 if not deleteSelected() and self.CursorPos > 0 then
2015 self.Text = string.sub( self.Text, 1, self.CursorPos - 1 ) .. string.sub( self.Text, self.CursorPos + 1 )
2016 self.CursorPos = self.CursorPos - 1
2017 if self.Numerical then
2018 self.Text = tostring(tonumber(self.Text))
2019 if self.Text == 'nil' then
2020 self.Text = '1'
2021 end
2022 end
2023 if self.OnChange then
2024 self:OnChange(event, keychar)
2025 end
2026 end
2027 elseif keychar == keys.home then
2028 -- Home
2029 self.CursorPos = 0
2030 if self.OnChange then
2031 self:OnChange(event, keychar)
2032 end
2033 elseif keychar == keys.delete then
2034 if not deleteSelected() and self.CursorPos < string.len(self.Text) then
2035 self.Text = string.sub( self.Text, 1, self.CursorPos ) .. string.sub( self.Text, self.CursorPos + 2 )
2036 if self.Numerical then
2037 self.Text = tostring(tonumber(self.Text))
2038 if self.Text == 'nil' then
2039 self.Text = '1'
2040 end
2041 end
2042 if self.OnChange then
2043 self:OnChange(keychar)
2044 end
2045 end
2046 elseif keychar == keys["end"] then
2047 -- End
2048 self.CursorPos = string.len(self.Text)
2049 else
2050 if self.OnChange then
2051 self:OnChange(event, keychar)
2052 end
2053 return false
2054 end
2055 end
2056end
2057]],
2058["View"] = [[
2059BackgroundColour = colours.transparent
2060Children = {}
2061
2062OnDraw = function(self, x, y)
2063 if self.BackgroundColour then
2064 Drawing.DrawBlankArea(x, y, self.Width, self.Height, self.BackgroundColour)
2065 end
2066end
2067
2068OnInitialise = function(self)
2069 self.Children = {}
2070end
2071
2072InitialiseFile = function(self, bedrock, file, name)
2073 local _new = {}
2074 _new.X = 1
2075 _new.Y = 1
2076 _new.Width = Drawing.Screen.Width
2077 _new.Height = Drawing.Screen.Height
2078 _new.BackgroundColour = file.BackgroundColour
2079 _new.Name = name
2080 _new.Children = {}
2081 _new.Bedrock = bedrock
2082 local new = self:Initialise(_new)
2083 for i, obj in ipairs(file.Children) do
2084 local view = bedrock:ObjectFromFile(obj, new)
2085 if not view.Z then
2086 view.Z = i
2087 end
2088 view.Parent = new
2089 table.insert(new.Children, view)
2090 end
2091 return new
2092end
2093
2094function CheckClick(self, object, x, y)
2095 local offset = {X = 0, Y = 0}
2096 if not object.Fixed and self.ChildOffset then
2097 offset = self.ChildOffset
2098 end
2099 if object.X + offset.X <= x and object.Y + offset.Y <= y and object.X + offset.X + object.Width > x and object.Y + offset.Y + object.Height > y then
2100 return true
2101 end
2102end
2103
2104function DoClick(self, object, event, side, x, y)
2105 if object then
2106 if self:CheckClick(object, x, y) then
2107 local offset = {X = 0, Y = 0}
2108 if not object.Fixed and self.ChildOffset then
2109 offset = self.ChildOffset
2110 end
2111 return object:Click(event, side, x - object.X - offset.X + 1, y - object.Y + 1 - offset.Y)
2112 end
2113 end
2114end
2115
2116Click = function(self, event, side, x, y, z)
2117 if self.Visible and not self.IgnoreClick then
2118 for i = #self.Children, 1, -1 do --children are ordered from smallest Z to highest, so this is done in reverse
2119 local child = self.Children[i]
2120 if self:DoClick(child, event, side, x, y) then
2121 if self.OnChildClick then
2122 self:OnChildClick(child, event, side, x, y)
2123 end
2124 return true
2125 end
2126 end
2127 if event == 'mouse_click' and self.OnClick and self:OnClick(event, side, x, y) ~= false then
2128 return true
2129 elseif event == 'mouse_drag' and self.OnDrag and self:OnDrag(event, side, x, y) ~= false then
2130 return true
2131 elseif event == 'mouse_scroll' and self.OnScroll and self:OnScroll(event, side, x, y) ~= false then
2132 return true
2133 else
2134 return false
2135 end
2136 else
2137 return false
2138 end
2139end
2140
2141OnRemove = function(self)
2142 if self == self.Bedrock:GetActiveObject() then
2143 self.Bedrock:SetActiveObject()
2144 end
2145 for i, child in ipairs(self.Children) do
2146 child:OnRemove()
2147 end
2148end
2149
2150local function findObjectNamed(view, name, minI)
2151 local minI = minI or 0
2152 if view and view.Children then
2153 for i, child in ipairs(view.Children) do
2154 if child.Name == name or child == name then
2155 return child, i, view
2156 elseif child.Children then
2157 local found, index, foundView = findObjectNamed(child, name)
2158 if found and minI <= index then
2159 return found, index, foundView
2160 end
2161 end
2162 end
2163 end
2164end
2165
2166function ReorderObjects(self)
2167 if self.Children then
2168 table.sort(self.Children, function(a,b)
2169 return a.Z < b.Z
2170 end)
2171 for i, v in ipairs(self.Children) do
2172 if v.ReorderObjects then
2173 v:ReorderObjects()
2174 end
2175 end
2176 end
2177end
2178
2179function AddObject(self, info, extra, first)
2180 if type(info) == 'string' then
2181 local h = fs.open(self.Bedrock.ViewPath..info..'.view', 'r')
2182 if h then
2183 info = textutils.unserialize(h.readAll())
2184 h.close()
2185 else
2186 error('Error in opening object: '..info)
2187 end
2188 end
2189
2190 if extra then
2191 for k, v in pairs(extra) do
2192 if v then
2193 info[k] = v
2194 end
2195 end
2196 end
2197
2198 local view = self.Bedrock:ObjectFromFile(info, self)
2199 if not view.Z then
2200 if first then
2201 view.Z = 1
2202 else
2203 view.Z = #self.Children + 1
2204 end
2205 end
2206
2207 if first then
2208 table.insert(self.Children, 1, view)
2209 else
2210 table.insert(self.Children, view)
2211 end
2212 if self.Bedrock.View then
2213 self.Bedrock:ReorderObjects()
2214 end
2215 self:ForceDraw()
2216 return view
2217end
2218
2219function GetObject(self, name)
2220 return findObjectNamed(self, name)
2221end
2222
2223local function findObjects(view, name)
2224 local objects = {}
2225 if view and view.Children then
2226 for i, child in ipairs(view.Children) do
2227 if child.Name == name or child == name then
2228 table.insert(objects, child)
2229 elseif child.Children then
2230 local objs = findObjects(child, name)
2231 if objs then
2232 for i2, v in ipairs(objs) do
2233 table.insert(objects, v)
2234 end
2235 end
2236 end
2237 end
2238 end
2239 return objects
2240end
2241
2242function GetObjects(self, name)
2243 return findObjects(self, name)
2244end
2245
2246function RemoveObject(self, name)
2247 local obj, index, view = findObjectNamed(self, name, minI)
2248 if index then
2249 view.Children[index]:OnRemove()
2250 table.remove(view.Children, index)
2251 if view.OnUpdate then
2252 view:OnUpdate('Children')
2253 end
2254 return true
2255 end
2256 return false
2257end
2258
2259function RemoveObjects(self, name)
2260 local i = 1
2261 while self:RemoveObject(name) and i < 100 do
2262 i = i + 1
2263 end
2264
2265end
2266
2267function RemoveAllObjects(self)
2268 for i, child in ipairs(self.Children) do
2269 child:OnRemove()
2270 self.Children[i] = nil
2271 end
2272 self:ForceDraw()
2273end
2274]],
2275["Window"] = [[
2276Inherit = 'View'
2277
2278ToolBarColour = colours.lightGrey
2279ToolBarTextColour = colours.black
2280ShadowColour = colours.grey
2281Title = ''
2282Flashing = false
2283CanClose = true
2284OnCloseButton = nil
2285OldActiveObject = nil
2286
2287LoadView = function(self)
2288 local view = self:GetObject('View')
2289 if view.ToolBarColour then
2290 window.ToolBarColour = view.ToolBarColour
2291 end
2292 if view.ToolBarTextColour then
2293 window.ToolBarTextColour = view.ToolBarTextColour
2294 end
2295 view.X = 1
2296 view.Y = 2
2297
2298 view:ForceDraw()
2299 self:OnUpdate('View')
2300 if self.OnViewLoad then
2301 self.OnViewLoad(view)
2302 end
2303 self.OldActiveObject = self.Bedrock:GetActiveObject()
2304 self.Bedrock:SetActiveObject(view)
2305end
2306
2307SetView = function(self, view)
2308 self:RemoveObject('View')
2309 table.insert(self.Children, view)
2310 view.Parent = self
2311 self:LoadView()
2312end
2313
2314Flash = function(self)
2315 self.Flashing = true
2316 self:ForceDraw()
2317 self.Bedrock:StartTimer(function()self.Flashing = false end, 0.4)
2318end
2319
2320OnDraw = function(self, x, y)
2321 local toolBarColour = (self.Flashing and colours.grey or self.ToolBarColour)
2322 local toolBarTextColour = (self.Flashing and colours.black or self.ToolBarTextColour)
2323 if toolBarColour then
2324 Drawing.DrawBlankArea(x, y, self.Width, 1, toolBarColour)
2325 end
2326 if toolBarTextColour then
2327 local title = self.Bedrock.Helpers.TruncateString(self.Title, self.Width - 2)
2328 Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, title, toolBarTextColour, toolBarColour)
2329 end
2330 Drawing.IgnoreConstraint = true
2331 Drawing.DrawBlankArea(x + 1, y + 1, self.Width, self.Height, self.ShadowColour)
2332 Drawing.IgnoreConstraint = false
2333end
2334
2335Close = function(self)
2336 self.Bedrock:SetActiveObject(self.OldActiveObject)
2337 self.Bedrock.Window = nil
2338 self.Bedrock:RemoveObject(self)
2339 if self.OnClose then
2340 self:OnClose()
2341 end
2342 self = nil
2343end
2344
2345OnUpdate = function(self, value)
2346 if value == 'View' and self:GetObject('View') then
2347 self.Width = self:GetObject('View').Width
2348 self.Height = self:GetObject('View').Height + 1
2349 self.X = math.ceil((Drawing.Screen.Width - self.Width) / 2)
2350 self.Y = math.ceil((Drawing.Screen.Height - self.Height) / 2)
2351 elseif value == 'CanClose' then
2352 self:RemoveObject('CloseButton')
2353 if self.CanClose then
2354 local button = self:AddObject({X = 1, Y = 1, Width = 1, Height = 1, Type = 'Button', BackgroundColour = colours.red, TextColour = colours.white, Text = 'x', Name = 'CloseButton'})
2355 button.OnClick = function(btn)
2356 if self.OnCloseButton then
2357 self:OnCloseButton()
2358 end
2359 self:Close()
2360 end
2361 end
2362 end
2363end
2364]],
2365}
2366BasePath = ''
2367ProgramPath = nil
2368
2369-- Program functions...
2370
2371local function main(...)
2372 -- Code here...
2373end
2374
2375-- Run
2376local args = {...}
2377local _, err = pcall(function() main(unpack(args)) end)
2378if err then
2379 -- Make a nice error handling screen here...
2380 term.setBackgroundColor(colors.black)
2381 term.setTextColor(colors.white)
2382 term.clear()
2383 term.setCursorPos(1, 3)
2384 print(" An Error Has Occured! D:\n\n")
2385 print(" " .. tostring(err) .. "\n\n")
2386 print(" Press any key to exit...")
2387 os.pullEvent("key")
2388end
2389
2390function LoadAPIs(self)
2391 local function loadAPI(name, content)
2392 local env = setmetatable({}, { __index = getfenv() })
2393 local func, err = loadstring(content, name..' (Bedrock API)')
2394 if not func then
2395 return false, printError(err)
2396 end
2397 setfenv(func, env)
2398 func()
2399 local api = {}
2400 for k,v in pairs(env) do
2401 api[k] = v
2402 end
2403 _G[name] = api
2404 return true
2405 end
2406
2407 local env = getfenv()
2408 local function loadObject(name, content)
2409 loadAPI(name, content)
2410 if env[name].Inherit then
2411 if not getfenv()[env[name].Inherit] then
2412 if objects[env[name].Inherit] then
2413 loadObject(env[name].Inherit, objects[env[name].Inherit])
2414 elseif fs.exists(self.ProgramPath..'/Objects/'..env[name].Inherit..'.lua') then
2415 local h = fs.open(self.ProgramPath..'/Objects/'..env[name].Inherit..'.lua', 'r')
2416 loadObject(env[name].Inherit, h.readAll())
2417 h.close()
2418 loadObject(name, content)
2419 return
2420 end
2421 end
2422 env[name].__index = getfenv()[env[name].Inherit]
2423 else
2424 env[name].__index = Object
2425 end
2426 setmetatable(env[name], env[name])
2427 end
2428
2429 for k, v in pairs(apis) do
2430 loadAPI(k, v)
2431 if k == 'Helpers' then
2432 self.Helpers = Helpers
2433 end
2434 end
2435
2436 for k, v in pairs(objects) do
2437 loadObject(k, v)
2438 end
2439
2440 local privateObjPath = self.ProgramPath..'/Objects/'
2441 if fs.exists(privateObjPath) and fs.isDir(privateObjPath) then
2442 for i, v in ipairs(fs.list(privateObjPath)) do
2443 if v ~= '.DS_Store' then
2444 local name = string.match(v, '(%a+)%.?.-')
2445 local h = fs.open(privateObjPath..v, 'r')
2446 loadObject(name, h.readAll())
2447 h.close()
2448 end
2449 end
2450 end
2451
2452 local privateAPIPath = self.ProgramPath..'/APIs/'
2453 if fs.exists(privateAPIPath) and fs.isDir(privateAPIPath) then
2454 for i, v in ipairs(fs.list(privateAPIPath)) do
2455 if v ~= '.DS_Store' then
2456 local name = string.match(v, '(%a+)%.?.-')
2457 local h = fs.open(privateAPIPath..v, 'r')
2458 loadAPI(name, h.readAll())
2459 h.close()
2460 end
2461 end
2462 end
2463end
2464
2465AllowTerminate = true
2466
2467View = nil
2468Menu = nil
2469
2470ActiveObject = nil
2471
2472DrawTimer = nil
2473DrawTimerExpiry = 0
2474
2475IsDrawing = false
2476
2477Running = true
2478
2479DefaultView = 'main'
2480
2481AnimationEnabled = true
2482
2483EventHandlers = {
2484
2485}
2486
2487ObjectClickHandlers = {
2488
2489}
2490
2491ObjectUpdateHandlers = {
2492
2493}
2494
2495Timers = {
2496
2497}
2498
2499ModifierKeys = {}
2500KeyboardShortcuts = {}
2501
2502keys.leftCommand = 219
2503keys.rightCommand = 220
2504
2505function Initialise(self, programPath)
2506 self.ProgramPath = programPath or self.ProgramPath
2507 if not programPath then
2508 if self.ProgramPath then
2509 local prgPath = self.ProgramPath
2510 local prgName = fs.getName(prgPath)
2511 if prgPath:find('/') then
2512 self.ProgramPath = prgPath:sub(1, #prgPath-#prgName-1)
2513 self.ProgramPath = prgPath:sub(1, #prgPath-#prgName-1)
2514 else
2515 self.ProgramPath = ''
2516 end
2517 else
2518 self.ProgramPath = ''
2519 end
2520 end
2521 self:LoadAPIs()
2522 self.ViewPath = self.ProgramPath .. '/Views/'
2523 --first, check that the barebones APIs are available
2524 local requiredApis = {
2525 'Drawing',
2526 'View'
2527 }
2528 local env = getfenv()
2529 for i,v in ipairs(requiredApis) do
2530 if not env[v] then
2531 error('The API: '..v..' is not loaded. Please make sure you load it to use Bedrock.')
2532 end
2533 end
2534
2535 local copy = { }
2536 for k, v in pairs(self) do
2537 if k ~= 'Initialise' then
2538 copy[k] = v
2539 end
2540 end
2541 return setmetatable(copy, getmetatable(self))
2542end
2543
2544function HandleClick(self, event, side, x, y)
2545 if self.Menu then
2546 if not self.View:DoClick(self.Menu, event, side, x, y) then
2547 self.Menu:Close()
2548 end
2549 elseif self.Window then
2550 if not self.View:CheckClick(self.Window, x, y) then
2551 self.Window:Flash()
2552 else
2553 self.View:DoClick(self.Window, event, side, x, y)
2554 end
2555 elseif self.View then
2556 if self.View:Click(event, side, x, y) ~= false then
2557 end
2558 end
2559end
2560
2561function UnregisterKeyboardShortcut(self, name)
2562 if name then
2563 self.KeyboardShortcuts[name] = nil
2564 end
2565end
2566
2567function RegisterKeyboardShortcut(self, keys, func, name)
2568 name = name or tostring(math.random(1, 10000))
2569 if type(keys[1]) == 'table' then
2570 for i, v in ipairs(keys) do
2571 self.KeyboardShortcuts[name] = {Keys = v, Function = func}
2572 end
2573 else
2574 self.KeyboardShortcuts[name] = {Keys = keys, Function = func}
2575 end
2576 return name
2577end
2578
2579function TryKeyboardShortcuts(self, keychar)
2580 if keychar == keys.backspace then
2581 keychar = keys.delete
2582 end
2583
2584 local len = 1 -- + keychar
2585 for k, v in pairs(self.ModifierKeys) do
2586 len = len + 1
2587 end
2588
2589 for _, shortcut in pairs(self.KeyboardShortcuts) do
2590 local match = true
2591 for i2, key in ipairs(shortcut.Keys) do
2592 if self.ModifierKeys[key] == nil and key ~= keychar then
2593 match = false
2594 end
2595 end
2596
2597 if match and #shortcut.Keys == len then
2598 return shortcut.Function() ~= false
2599 end
2600 end
2601end
2602
2603function HandleKeyChar(self, event, keychar)
2604 if keychar == keys.leftCtrl or keychar == keys.leftShift or keychar == keys.leftAlt or keychar == keys.leftCommand or keychar == keys.rightCommand or keychar == keys.rightCtrl or keychar == keys.rightShift or keychar == keys.rightAlt then
2605 if keychar == keys.leftCommand or keychar == keys.rightCommand or keychar == keys.rightCtrl then
2606 keychar = keys.leftCtrl
2607 elseif keychar == keys.rightAlt then
2608 keychar = keys.leftAlt
2609 elseif keychar == keys.rightShift then
2610 keychar = keys.leftShift
2611 end
2612 self.ModifierKeys[keychar] = self:StartTimer(function(_, timer)
2613 if timer == self.ModifierKeys[keychar] then
2614 self.ModifierKeys[keychar] = nil
2615 end
2616 end, 1)
2617 elseif self:TryKeyboardShortcuts(keychar) then
2618 return
2619 end
2620
2621 if self:GetActiveObject() then
2622 local activeObject = self:GetActiveObject()
2623 if activeObject.OnKeyChar then
2624 if activeObject:OnKeyChar(event, keychar) ~= false then
2625 --self:Draw()
2626 end
2627 end
2628 end
2629end
2630
2631PreparedMenus = {}
2632
2633function PrepareMenu(self, name)
2634 local menu = self:AddObject(name, {Type = 'Menu', X = 1, Y = 1, Prepared = true})
2635 menu.Visible = false
2636 self.PreparedMenus[name] = menu
2637 return menu
2638end
2639
2640function ToggleMenu(self, name, owner, x, y)
2641 if self.Menu then
2642 self.Menu:Close()
2643 return false
2644 else
2645 self:SetMenu(name, owner, x, y)
2646 return true
2647 end
2648end
2649
2650function SetMenu(self, menu, owner, x, y)
2651 x = x or 1
2652 y = y or 1
2653 if self.Menu then
2654 self.Menu:Close()
2655 end
2656 if menu then
2657 local pos = owner:GetPosition()
2658 if self.PreparedMenus[menu] then
2659 self.Menu = self.PreparedMenus[menu]
2660 self.Menu.Visible = true
2661 self.Menu.Owner = owner
2662 self.Menu.X = pos.X + x - 1
2663 self.Menu.Y = pos.Y + y
2664 self.Menu.Z = self.View.Children[#self.View.Children].Z + 1
2665 self:ReorderObjects()
2666 else
2667 self.Menu = self:AddObject(menu, {Type = 'Menu', Owner = owner, X = pos.X + x - 1, Y = pos.Y + y, Z = self.View.Children[#self.View.Children].Z + 1})
2668 end
2669 end
2670end
2671
2672function ObjectClick(self, name, func)
2673 self.ObjectClickHandlers[name] = func
2674end
2675
2676function ClickObject(self, object, event, side, x, y)
2677 if self.ObjectClickHandlers[object.Name] then
2678 return self.ObjectClickHandlers[object.Name](object, event, side, x, y)
2679 end
2680 return false
2681end
2682
2683function ObjectUpdate(self, name, func)
2684 self.ObjectUpdateHandlers[name] = func
2685end
2686
2687function UpdateObject(self, object, ...)
2688 if self.ObjectUpdateHandlers[object.Name] then
2689 self.ObjectUpdateHandlers[object.Name](object, ...)
2690 --self:Draw()
2691 end
2692end
2693
2694function GetAbsolutePosition(self, obj)
2695 if not obj.Parent then
2696 return {X = obj.X, Y = obj.Y}
2697 else
2698 local pos = self:GetAbsolutePosition(obj.Parent)
2699 local x = pos.X + obj.X - 1
2700 local y = pos.Y + obj.Y - 1
2701 if not obj.Fixed and obj.Parent.ChildOffset then
2702 x = x + obj.Parent.ChildOffset.X
2703 y = y + obj.Parent.ChildOffset.Y
2704 end
2705 return {X = x, Y = y}
2706 end
2707end
2708
2709function LoadView(self, name, draw)
2710 if self.View and self.OnViewClose then
2711 self.OnViewClose(self.View.Name)
2712 end
2713 if self.View then
2714 self.View:OnRemove()
2715 end
2716 local success = false
2717
2718 if Drawing.Screen.Width <= 26 and fs.exists(self.ViewPath..name..'-pocket.view') then
2719 name = name..'-pocket'
2720 elseif Drawing.Screen.Width <= 39 and fs.exists(self.ViewPath..name..'-turtle.view') then
2721 name = name..'-turtle'
2722 end
2723
2724 if not fs.exists(self.ViewPath..name..'.view') then
2725 error('The view: '..name..'.view does not exist.')
2726 end
2727
2728 local h = fs.open(self.ViewPath..name..'.view', 'r')
2729 if h then
2730 local view = textutils.unserialize(h.readAll())
2731 h.close()
2732 if view then
2733 self.View = View:InitialiseFile(self, view, name)
2734 self:ReorderObjects()
2735
2736 if OneOS and view.ToolBarColour then
2737 OneOS.ToolBarColour = view.ToolBarColour
2738 end
2739 if OneOS and view.ToolBarTextColour then
2740 OneOS.ToolBarTextColour = view.ToolBarTextColour
2741 end
2742 if not self:GetActiveObject() then
2743 self:SetActiveObject()
2744 end
2745 success = true
2746 end
2747 end
2748
2749 if success and self.OnViewLoad then
2750 self.OnViewLoad(name)
2751 end
2752
2753 if draw ~= false then
2754 self:Draw()
2755 end
2756
2757 if not success then
2758 error('Failed to load view: '..name..'. It probably isn\'t formatted correctly. Did you forget a } or ,?')
2759 end
2760
2761 return success
2762end
2763
2764function InheritFile(self, file, name)
2765 local h = fs.open(self.ViewPath..name..'.view', 'r')
2766 if h then
2767 local super = textutils.unserialize(h.readAll())
2768 if super then
2769 if type(super) ~= 'table' then
2770 error('View: "'..name..'.view" is not formatted correctly.')
2771 end
2772
2773 for k, v in pairs(super) do
2774 if not file[k] then
2775 file[k] = v
2776 end
2777 end
2778 return file
2779 end
2780 end
2781 return file
2782end
2783
2784function ParseStringSize(self, parent, k, v)
2785 local parentSize = parent.Width
2786 if k == 'Height' or k == 'Y' then
2787 parentSize = parent.Height
2788 end
2789 local parts = {v}
2790 if type(v) == 'string' and string.find(v, ',') then
2791 parts = {}
2792 for word in string.gmatch(v, '([^,]+)') do
2793 table.insert(parts, word)
2794 end
2795 end
2796
2797 v = 0
2798 for i2, part in ipairs(parts) do
2799 if type(part) == 'string' and part:sub(#part) == '%' then
2800 v = v + math.ceil(parentSize * (tonumber(part:sub(1, #part-1)) / 100))
2801 else
2802 v = v + tonumber(part)
2803 end
2804 end
2805 return v
2806end
2807
2808function ObjectFromFile(self, file, view)
2809 local env = getfenv()
2810 if env[file.Type] then
2811 if not env[file.Type].Initialise then
2812 error('Malformed Object: '..file.Type)
2813 end
2814 local object = {}
2815
2816 if file.InheritView then
2817 file = self:InheritFile(file, file.InheritView)
2818 end
2819
2820 object.AutoWidth = true
2821 for k, v in pairs(file) do
2822 if k == 'Width' or k == 'X' or k == 'Height' or k == 'Y' then
2823 v = self:ParseStringSize(view, k, v)
2824 end
2825
2826 if k == 'Width' then
2827 object.AutoWidth = false
2828 end
2829 if k ~= 'Children' then
2830 object[k] = v
2831 else
2832 object[k] = {}
2833 end
2834 end
2835
2836 object.Parent = view
2837 object.Bedrock = self
2838 if not object.Name then
2839 object.Name = file.Type
2840 end
2841
2842 object = env[file.Type]:Initialise(object)
2843
2844 if file.Children then
2845 for i, obj in ipairs(file.Children) do
2846 local _view = self:ObjectFromFile(obj, object)
2847 if not _view.Z then
2848 _view.Z = i
2849 end
2850 _view.Parent = object
2851 table.insert(object.Children, _view)
2852 end
2853 end
2854
2855 if not object.OnClick then
2856 object.OnClick = function(...) return self:ClickObject(...) end
2857 end
2858 --object.OnUpdate = function(...) self:UpdateObject(...) end
2859
2860 if object.OnUpdate then
2861 for k, v in pairs(env[file.Type]) do
2862 object:OnUpdate(k)
2863 end
2864
2865 for k, v in pairs(object.__index) do
2866 object:OnUpdate(k)
2867 end
2868 end
2869
2870 if object.Active then
2871 object.Bedrock:SetActiveObject(object)
2872 end
2873 if object.OnLoad then
2874 object:OnLoad()
2875 end
2876 object.Ready = true
2877 return object
2878 elseif not file.Type then
2879 error('No object type specified. (e.g. Type = "Button")')
2880 else
2881 error('No Object: '..file.Type..'. The API probably isn\'t loaded')
2882 end
2883end
2884
2885function ReorderObjects(self)
2886 if self.View and self.View.Children then
2887 self.View:ReorderObjects()
2888 end
2889end
2890
2891function AddObject(self, info, extra, first)
2892 return self.View:AddObject(info, extra, first)
2893end
2894
2895function GetObject(self, name)
2896 return self.View:GetObject(name)
2897end
2898
2899function GetObjects(self, name)
2900 return self.View:GetObjects(name)
2901end
2902
2903function RemoveObject(self, name)
2904 return self.View:RemoveObject(name)
2905end
2906
2907function RemoveObjects(self, name)
2908 return self.View:RemoveObjects(name)
2909end
2910
2911DrawEvent = nil
2912
2913function HandleDraw(self, event, id)
2914 if id == self.DrawEvent then
2915 self.DrawEvent = nil
2916 self:Draw()
2917 end
2918end
2919
2920function ForceDraw(self)
2921 if not self.DrawEvent then--or self.DrawTimerExpiry <= os.clock() then
2922 self.DrawEvent = math.random()
2923 os.queueEvent('bedrock_draw', self.DrawEvent)
2924 -- self:StartTimer(function()
2925 -- self.DrawTimer = nil
2926 -- self:Draw()
2927 -- end, 0.05)
2928 -- self.DrawTimerExpiry = os.clock() + 0.1
2929 end
2930end
2931
2932function DisplayWindow(self, _view, title, canClose)
2933 if canClose == nil then
2934 canClose = true
2935 end
2936 if type(_view) == 'string' then
2937 local h = fs.open(self.ViewPath.._view..'.view', 'r')
2938 if h then
2939 _view = textutils.unserialize(h.readAll())
2940 h.close()
2941 end
2942 end
2943
2944 self.Window = self:AddObject({Type = 'Window', Z = 999, Title = title, CanClose = canClose})
2945 _view.Type = 'View'
2946 _view.Name = 'View'
2947 _view.BackgroundColour = _view.BackgroundColour or colours.white
2948 self.Window:SetView(self:ObjectFromFile(_view, self.Window))
2949end
2950
2951function DisplayAlertWindow(self, title, text, buttons, callback)
2952 local func = function(btn)
2953 self.Window:Close()
2954 if callback then
2955 callback(btn.Text)
2956 end
2957 end
2958 local children = {}
2959 local usedX = -1
2960 if buttons then
2961 for i, text in ipairs(buttons) do
2962 usedX = usedX + 3 + #text
2963 table.insert(children, {
2964 ["Y"]="100%,-1",
2965 ["X"]="100%,-"..usedX,
2966 ["Name"]=text.."Button",
2967 ["Type"]="Button",
2968 ["Text"]=text,
2969 OnClick = func
2970 })
2971 end
2972 end
2973
2974 local width = usedX + 2
2975 if width < 28 then
2976 width = 28
2977 end
2978
2979 local canClose = true
2980 if buttons and #buttons~=0 then
2981 canClose = false
2982 end
2983
2984 local height = 0
2985 if text then
2986 height = #Helpers.WrapText(text, width - 2)
2987 table.insert(children, {
2988 ["Y"]=2,
2989 ["X"]=2,
2990 ["Width"]="100%,-2",
2991 ["Height"]=height,
2992 ["Name"]="Label",
2993 ["Type"]="Label",
2994 ["Text"]=text
2995 })
2996 end
2997 local view = {
2998 Children = children,
2999 Width=width,
3000 Height=3+height+(canClose and 0 or 1),
3001 OnKeyChar = function(_view, keychar)
3002 func({Text=buttons[1]})
3003 end
3004 }
3005 self:DisplayWindow(view, title, canClose)
3006end
3007
3008function DisplayTextBoxWindow(self, title, text, callback, textboxText, cursorAtEnd)
3009 textboxText = textboxText or ''
3010 local children = {
3011 {
3012 ["Y"]="100%,-1",
3013 ["X"]="100%,-4",
3014 ["Name"]="OkButton",
3015 ["Type"]="Button",
3016 ["Text"]="Ok",
3017 OnClick = function()
3018 local text = self.Window:GetObject('TextBox').Text
3019 self.Window:Close()
3020 callback(true, text)
3021 end
3022 },
3023 {
3024 ["Y"]="100%,-1",
3025 ["X"]="100%,-13",
3026 ["Name"]="CancelButton",
3027 ["Type"]="Button",
3028 ["Text"]="Cancel",
3029 OnClick = function()
3030 self.Window:Close()
3031 callback(false)
3032 end
3033 }
3034 }
3035
3036 local height = -1
3037 if text and #text ~= 0 then
3038 height = #Helpers.WrapText(text, 26)
3039 table.insert(children, {
3040 ["Y"]=2,
3041 ["X"]=2,
3042 ["Width"]="100%,-2",
3043 ["Height"]=height,
3044 ["Name"]="Label",
3045 ["Type"]="Label",
3046 ["Text"]=text
3047 })
3048 end
3049 table.insert(children,
3050 {
3051 ["Y"]=3+height,
3052 ["X"]=2,
3053 ["Width"]="100%,-2",
3054 ["Name"]="TextBox",
3055 ["Type"]="TextBox",
3056 ["Text"]=textboxText,
3057 ["CursorPos"]=(cursorAtEnd or 0)
3058 })
3059 local view = {
3060 Children = children,
3061 Width=28,
3062 Height=5+height+(canClose and 0 or 1),
3063 }
3064 self:DisplayWindow(view, title)
3065 self.Window:GetObject('TextBox').OnChange = function(txtbox, event, keychar)
3066 if keychar == keys.enter then
3067 self.Window:Close()
3068 callback(true, txtbox.Text)
3069 end
3070 end
3071 self:SetActiveObject(self.Window:GetObject('TextBox'))
3072 self.Window.OnCloseButton = function()callback(false)end
3073end
3074
3075function DisplayOpenFileWindow(self, title, callback)
3076 title = title or 'Open File'
3077 local func = function(btn)
3078 self.Window:Close()
3079 if callback then
3080 callback(btn.Text)
3081 end
3082 end
3083
3084 local sidebarItems = {}
3085
3086 --this is a really, really super bad way of doing it
3087 local separator = ' !'
3088
3089 local function addFolder(path, level)
3090 for i, v in ipairs(_fs.list(path)) do
3091 local fPath = path .. '/' .. v
3092 if fPath ~= '/rom' and _fs.isDir(fPath) then
3093 table.insert(sidebarItems, level .. v..separator..fPath)
3094 addFolder(fPath, level .. ' ')
3095 end
3096 end
3097 end
3098
3099 if OneOS then
3100 _fs = OneOS.FS
3101 end
3102
3103 addFolder('','')
3104
3105 local currentFolder = ''
3106 local selectedPath = nil
3107
3108 local goToFolder = nil
3109
3110 local children = {
3111 {
3112 ["Y"]="100%,-2",
3113 ["X"]=1,
3114 ["Height"]=3,
3115 ["Width"]="100%",
3116 ["BackgroundColour"]=colours.lightGrey,
3117 ["Name"]="SidebarListView",
3118 ["Type"]="View"
3119 },
3120 {
3121 ["Y"]="100%,-1",
3122 ["X"]="100%,-4",
3123 ["Name"]="OkButton",
3124 ["Type"]="Button",
3125 ["Text"]="Ok",
3126 ["BackgroundColour"]=colours.white,
3127 ["Enabled"]=false,
3128 OnClick = function()
3129 if selectedPath then
3130 self.Window:Close()
3131 callback(true, Helpers.TidyPath(selectedPath))
3132 end
3133 end
3134 },
3135 {
3136 ["Y"]="100%,-1",
3137 ["X"]="100%,-13",
3138 ["Name"]="CancelButton",
3139 ["Type"]="Button",
3140 ["Text"]="Cancel",
3141 ["BackgroundColour"]=colours.white,
3142 OnClick = function()
3143 self.Window:Close()
3144 callback(false)
3145 end
3146 },
3147 {
3148 ["Y"]=1,
3149 ["X"]=1,
3150 ["Height"]="100%,-3",
3151 ["Width"]="40%,-1",
3152 ["Name"]="SidebarListView",
3153 ["Type"]="ListView",
3154 ["CanSelect"]=true,
3155 ["Items"]={
3156 ["Computer"] = sidebarItems
3157 },
3158 OnSelect = function(listView, text)
3159 local _,s = text:find(separator)
3160 if s then
3161 local path = text:sub(s + 1)
3162 goToFolder(path)
3163 end
3164 end,
3165 OnClick = function(listView, event, side, x, y)
3166 if y == 1 then
3167 goToFolder('/')
3168 end
3169 end
3170 },
3171 {
3172 ["Y"]=1,
3173 ["X"]="40%",
3174 ["Height"]="100%,-3",
3175 ["Width"]=1,
3176 ["Type"]="Separator"
3177 },
3178 {
3179 ["Y"]=1,
3180 ["X"]="40%,2",
3181 ["Width"]="65%,-3",
3182 ["Height"]=1,
3183 ["Type"]="Label",
3184 ["Name"]="PathLabel",
3185 ["TextColour"]=colours.lightGrey,
3186 ["Text"]='/'
3187 },
3188 {
3189 ["Y"]=2,
3190 ["X"]="40%,1",
3191 ["Height"]="100%,-4",
3192 ["Width"]="65%,-1",
3193 ["Name"]="FilesListView",
3194 ["Type"]="ListView",
3195 ["CanSelect"]=true,
3196 ["Items"]={},
3197 OnSelect = function(listView, text)
3198 selectedPath = Helpers.TidyPath(currentFolder .. '/' .. text)
3199 self.Window:GetObject('OkButton').Enabled = true
3200 end,
3201 OnClick = function(listView, event, side, x, y)
3202 if y == 1 then
3203 goToFolder('/')
3204 end
3205 end
3206 },
3207 }
3208 local view = {
3209 Children = children,
3210 Width=40,
3211 Height= Drawing.Screen.Height - 4,
3212 OnCloseButton=function()
3213 callback(false)
3214 end
3215 }
3216 self:DisplayWindow(view, title)
3217
3218 goToFolder = function(path)
3219 path = Helpers.TidyPath(path)
3220 self.Window:GetObject('PathLabel').Text = path
3221 currentFolder = path
3222
3223 local filesListItems = {}
3224 for i, v in ipairs(_fs.list(path)) do
3225 if not _fs.isDir(currentFolder .. v) then
3226 table.insert(filesListItems, v)
3227 end
3228 end
3229 self.Window:GetObject('OkButton').Enabled = false
3230 selectedPath = nil
3231 self.Window:GetObject('FilesListView').Items = filesListItems
3232
3233 end
3234
3235 if startPath then
3236 goToFolder(startPath)
3237 elseif OneOS then
3238 goToFolder('/Desktop/Documents/')
3239 else
3240 goToFolder('')
3241 end
3242
3243 self.Window.OnCloseButton = function()callback(false)end
3244end
3245
3246function DisplaySaveFileWindow(self, title, callback, extension, startPath)
3247 local _fs = fs
3248 if extension and extension:sub(1,1) ~= '.' then
3249 extension = '.' .. extension
3250 end
3251 extension = extension or ''
3252
3253 title = title or 'Save File'
3254 local func = function(btn)
3255 self.Window:Close()
3256 if callback then
3257 callback(btn.Text)
3258 end
3259 end
3260
3261 local sidebarItems = {}
3262
3263 --this is a really, really super bad way of doing it
3264 local separator = ' !'
3265
3266 local function addFolder(path, level)
3267 for i, v in ipairs(_fs.list(path)) do
3268 local fPath = path .. '/' .. v
3269 if fPath ~= '/rom' and _fs.isDir(fPath) then
3270 table.insert(sidebarItems, level .. v..separator..fPath)
3271 addFolder(fPath, level .. ' ')
3272 end
3273 end
3274 end
3275
3276 if OneOS then
3277 _fs = OneOS.FS
3278 end
3279 addFolder('','')
3280
3281 local currentFolder = ''
3282 local selectedPath = nil
3283
3284 local goToFolder = nil
3285
3286 local function updatePath()
3287 local text = self:GetObject('FileNameTextBox').Text
3288 if #text == 0 then
3289 self.Window:GetObject('OkButton').Enabled = false
3290 selectedPath = Helpers.TidyPath(currentFolder)
3291 else
3292 self.Window:GetObject('OkButton').Enabled = true
3293 selectedPath = Helpers.TidyPath(currentFolder .. '/' .. text .. extension)
3294 end
3295 self:GetObject('PathLabel').Text = selectedPath
3296 end
3297
3298 local children = {
3299 {
3300 ["Y"]="100%,-2",
3301 ["X"]=1,
3302 ["Height"]=3,
3303 ["Width"]="100%",
3304 ["BackgroundColour"]=colours.lightGrey,
3305 ["Type"]="View"
3306 },
3307 {
3308 ["Y"]="100%,-1",
3309 ["X"]="100%,-4",
3310 ["Name"]="OkButton",
3311 ["Type"]="Button",
3312 ["Text"]="Ok",
3313 ["BackgroundColour"]=colours.white,
3314 ["Enabled"]=false,
3315 OnClick = function()
3316 if selectedPath then
3317 local text = self:GetObject('FileNameTextBox').Text
3318 self.Window:Close()
3319 callback(true, selectedPath, text)
3320 end
3321 end
3322 },
3323 {
3324 ["Y"]="100%,-1",
3325 ["X"]="100%,-13",
3326 ["Name"]="CancelButton",
3327 ["Type"]="Button",
3328 ["Text"]="Cancel",
3329 ["BackgroundColour"]=colours.white,
3330 OnClick = function()
3331 self.Window:Close()
3332 callback(false)
3333 end
3334 },
3335 {
3336 ["Y"]="100%,-2",
3337 ["X"]=3,
3338 ["Width"]="100%,-4",
3339 ["Name"]="PathLabel",
3340 ["Type"]="Label",
3341 ["Text"]="/",
3342 ["TextColour"]=colours.grey
3343 },
3344 {
3345 ["Y"]="100%,-1",
3346 ["X"]=3,
3347 ["Width"]="100%,-17",
3348 ["Name"]="FileNameTextBox",
3349 ["Type"]="TextBox",
3350 ["Placeholder"]="File Name",
3351 ["Active"]=true,
3352 ["BackgroundColour"]=colours.white,
3353 OnChange = function(_self, event, keychar)
3354 if keychar == keys.enter then
3355 self:GetObject('OkButton'):OnClick()
3356 else
3357 updatePath()
3358 end
3359 end
3360 },
3361 {
3362 ["Y"]=1,
3363 ["X"]=2,
3364 ["Height"]="100%,-3",
3365 ["Width"]="100%,-1",
3366 ["Name"]="SidebarListView",
3367 ["Type"]="ListView",
3368 ["CanSelect"]=true,
3369 ["Items"]={
3370 ["Computer"] = sidebarItems
3371 },
3372 OnSelect = function(listView, text)
3373 local _,s = text:find(separator)
3374 if s then
3375 local path = text:sub(s + 1)
3376 goToFolder(path)
3377 end
3378 end,
3379 OnClick = function(listView, event, side, x, y)
3380 if y == 1 then
3381 goToFolder('/')
3382 end
3383 end
3384 },
3385 }
3386 local view = {
3387 Children = children,
3388 Width=35,
3389 Height= Drawing.Screen.Height - 4,
3390 OnCloseButton=function()
3391 callback(false)
3392 end
3393 }
3394 self:DisplayWindow(view, title)
3395
3396 self:SetActiveObject(self.Window:GetObject('FileNameTextBox'))
3397
3398 goToFolder = function(path)
3399 path = Helpers.TidyPath(path)
3400 currentFolder = path
3401 selectedPath = nil
3402 updatePath()
3403 end
3404
3405 if startPath then
3406 goToFolder(startPath)
3407 elseif OneOS then
3408 goToFolder('/Desktop/Documents/')
3409 else
3410 goToFolder('')
3411 end
3412
3413 self.Window.OnCloseButton = function()callback(false)end
3414end
3415
3416function RegisterEvent(self, event, func)
3417 if not self.EventHandlers[event] then
3418 self.EventHandlers[event] = {}
3419 end
3420 table.insert(self.EventHandlers[event], func)
3421end
3422
3423function StartRepeatingTimer(self, func, interval)
3424 local int = interval
3425 if type(int) == 'function' then
3426 int = int()
3427 end
3428 if not int or int <= 0 then
3429 return
3430 end
3431 local timer = os.startTimer(int)
3432
3433 self.Timers[timer] = {func, true, interval}
3434 return timer
3435end
3436
3437function StartTimer(self, func, delay)
3438 local timer = os.startTimer(delay)
3439 self.Timers[timer] = {func, false}
3440 return timer
3441end
3442
3443function StopTimer(self, timer)
3444 if self.Timers[timer] then
3445 self.Timers[timer] = nil
3446 end
3447end
3448
3449function HandleTimer(self, event, timer)
3450 if self.Timers[timer] then
3451 local oldTimer = self.Timers[timer]
3452 self.Timers[timer] = nil
3453 local new = nil
3454 if oldTimer[2] then
3455 new = self:StartRepeatingTimer(oldTimer[1], oldTimer[3])
3456 end
3457 if oldTimer and oldTimer[1] then
3458 oldTimer[1](new, timer)
3459 end
3460 elseif self.OnTimer then
3461 self.OnTimer(self, event, timer)
3462 end
3463end
3464
3465function SetActiveObject(self, object)
3466 if object then
3467 if object ~= self.ActiveObject then
3468 self.ActiveObject = object
3469 object:ForceDraw()
3470 end
3471 elseif self.ActiveObject ~= nil then
3472 self.ActiveObject = nil
3473 self.CursorPos = nil
3474 self.View:ForceDraw()
3475 end
3476end
3477
3478function GetActiveObject(self)
3479 return self.ActiveObject
3480end
3481
3482OnTimer = nil
3483OnClick = nil
3484OnKeyChar = nil
3485OnDrag = nil
3486OnScroll = nil
3487OnViewLoad = nil
3488OnViewClose = nil
3489OnDraw = nil
3490OnQuit = nil
3491
3492local eventFuncs = {
3493 OnClick = {'mouse_click', 'monitor_touch'},
3494 OnKeyChar = {'key', 'char'},
3495 OnDrag = {'mouse_drag'},
3496 OnScroll = {'mouse_scroll'},
3497 HandleClick = {'mouse_click', 'mouse_drag', 'mouse_scroll', 'monitor_touch'},
3498 HandleKeyChar = {'key', 'char'},
3499 HandleTimer = {'timer'},
3500 HandleDraw = {'bedrock_draw'}
3501}
3502
3503local drawCalls = 0
3504local ignored = 0
3505function Draw(self)
3506 self.IsDrawing = true
3507 if self.OnDraw then
3508 self:OnDraw()
3509 end
3510
3511 if self.View and self.View:NeedsDraw() then
3512 self.View:Draw()
3513 Drawing.DrawBuffer()
3514 if isDebug then
3515 drawCalls = drawCalls + 1
3516 end
3517 elseif not self.View then
3518 print('No loaded view. You need to do program:LoadView first.')
3519 end
3520
3521 if self:GetActiveObject() and self.CursorPos and type(self.CursorPos[1]) == 'number' and type(self.CursorPos[2]) == 'number' then
3522 term.setCursorPos(self.CursorPos[1], self.CursorPos[2])
3523 term.setTextColour(self.CursorColour)
3524 term.setCursorBlink(true)
3525 else
3526 term.setCursorBlink(false)
3527 end
3528
3529 self.IsDrawing = false
3530end
3531
3532function EventHandler(self)
3533 local event = { os.pullEventRaw() }
3534
3535 if self.EventHandlers[event[1]] then
3536 for i, e in ipairs(self.EventHandlers[event[1]]) do
3537 e(self, unpack(event))
3538 end
3539 end
3540end
3541
3542function Quit(self)
3543 self.Running = false
3544 if self.OnQuit then
3545 self:OnQuit()
3546 end
3547 if OneOS then
3548 OneOS.Close()
3549 end
3550end
3551
3552function Run(self, ready)
3553 if not term.isColour or not term.isColour() then
3554 print('This program requires an advanced (golden) comptuer to run, sorry.')
3555 error('', 0)
3556 end
3557
3558 for name, events in pairs(eventFuncs) do
3559 if self[name] then
3560 for i, event in ipairs(events) do
3561 self:RegisterEvent(event, self[name])
3562 end
3563 end
3564 end
3565
3566 if self.AllowTerminate then
3567 self:RegisterEvent('terminate', function()error('Terminated', 0) end)
3568 end
3569
3570 if self.DefaultView and self.DefaultView ~= '' and fs.exists(self.ViewPath..self.DefaultView..'.view') then
3571 self:LoadView(self.DefaultView)
3572 end
3573
3574 if ready then
3575 ready()
3576 end
3577
3578 self:Draw()
3579
3580 while self.Running do
3581 self:EventHandler()
3582 end
3583end