aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Lee <alee14498@gmail.com>2019-03-05 19:26:59 -0500
committerAndrew Lee <alee14498@gmail.com>2019-03-05 19:26:59 -0500
commita18407bdb9c87ac970418cd625abe38f550ab45c (patch)
treecb40abeaef3b81dcd1cbf23304e80acf0445aed2
parent84a82712e4e3bd6826e572e63accc0800992eb4b (diff)
downloadbits-UI-a18407bdb9c87ac970418cd625abe38f550ab45c.tar.gz
bits-UI-a18407bdb9c87ac970418cd625abe38f550ab45c.tar.bz2
bits-UI-a18407bdb9c87ac970418cd625abe38f550ab45c.zip
Added a bunch of stuff
-rw-r--r--README.md11
-rw-r--r--programs/ink.lua3153
-rw-r--r--programs/ink.shortcut2
-rw-r--r--programs/luaide.shortcut2
-rw-r--r--programs/mousebrowser.lua1298
-rw-r--r--programs/mousebrowser.shortcut2
-rw-r--r--programs/npaintpro.lua2827
-rw-r--r--programs/npaintpro.shortcut2
-rw-r--r--startup74
-rw-r--r--startup.lua88
-rw-r--r--system/apis/flib.lua124
-rw-r--r--system/boot.lua16
-rw-r--r--system/desktop.lua15
-rw-r--r--system/setup.lua14
-rw-r--r--system/skel/README.txt7
15 files changed, 7560 insertions, 75 deletions
diff --git a/README.md b/README.md
index 4c481a4..babbc99 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,16 @@
# bits-UI
-An operating system for ComputerCraft!<br>
+An operating system for ComputerCraft.<br>
Feel free to contribute to this project!
+# Credits
+The programs/apis I used for bits-UI.
+
+- NPaintPro by NitrogenFingers
+- Ink by oeed
+- LuaIDE by GravityScore
+- Mouse File Browser by Stiepen irc(Kilobyte), Cruor and BigSHinyToys
+- fLib by NDFJay
+
# Installation
If you want to get this running be sure to have a ComputerCraft emulator such as <a href="http://www.computercraft.info/forums2/index.php?/topic/18789-ccemuredux-computercraft-emulator-redux/">CCEmuRedux</a> or <a href="https://emux.cc/">CCEmuX</a>. But if you want to run this in minecraft be sure to have CC-Tweaked.
diff --git a/programs/ink.lua b/programs/ink.lua
new file mode 100644
index 0000000..49f6119
--- /dev/null
+++ b/programs/ink.lua
@@ -0,0 +1,3153 @@
+tArgs = {...}
+
+if OneOS then
+ --running under OneOS
+ OneOS.ToolBarColour = colours.grey
+ OneOS.ToolBarTextColour = colours.white
+end
+
+local _w, _h = term.getSize()
+
+local round = function(num, idp)
+ local mult = 10^(idp or 0)
+ return math.floor(num * mult + 0.5) / mult
+end
+
+UIColours = {
+ Toolbar = colours.grey,
+ ToolbarText = colours.lightGrey,
+ ToolbarSelected = colours.lightBlue,
+ ControlText = colours.white,
+ ToolbarItemTitle = colours.black,
+ Background = colours.lightGrey,
+ MenuBackground = colours.white,
+ MenuText = colours.black,
+ MenuSeparatorText = colours.grey,
+ MenuDisabledText = colours.lightGrey,
+ Shadow = colours.grey,
+ TransparentBackgroundOne = colours.white,
+ TransparentBackgroundTwo = colours.lightGrey,
+ MenuBarActive = colours.white
+}
+
+local getNames = peripheral.getNames or function()
+ local tResults = {}
+ for n,sSide in ipairs( rs.getSides() ) do
+ if peripheral.isPresent( sSide ) then
+ table.insert( tResults, sSide )
+ local isWireless = false
+ if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then
+ isWireless = true
+ end
+ if peripheral.getType( sSide ) == "modem" and not isWireless then
+ local tRemote = peripheral.call( sSide, "getNamesRemote" )
+ for n,sName in ipairs( tRemote ) do
+ table.insert( tResults, sName )
+ end
+ end
+ end
+ end
+ return tResults
+end
+
+Peripheral = {
+ GetPeripheral = function(_type)
+ for i, p in ipairs(Peripheral.GetPeripherals()) do
+ if p.Type == _type then
+ return p
+ end
+ end
+ end,
+
+ Call = function(type, ...)
+ local tArgs = {...}
+ local p = Peripheral.GetPeripheral(type)
+ peripheral.call(p.Side, unpack(tArgs))
+ end,
+
+ GetPeripherals = function(filterType)
+ local peripherals = {}
+ for i, side in ipairs(getNames()) do
+ local name = peripheral.getType(side):gsub("^%l", string.upper)
+ local code = string.upper(side:sub(1,1))
+ if side:find('_') then
+ code = side:sub(side:find('_')+1)
+ end
+
+ local dupe = false
+ for i, v in ipairs(peripherals) do
+ if v[1] == name .. ' ' .. code then
+ dupe = true
+ end
+ end
+
+ if not dupe then
+ local _type = peripheral.getType(side)
+ local isWireless = false
+ if _type == 'modem' then
+ if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then
+ isWireless = true
+ end
+ if isWireless then
+ _type = 'wireless_modem'
+ name = 'W '..name
+ end
+ end
+ if not filterType or _type == filterType then
+ table.insert(peripherals, {Name = name:sub(1,8) .. ' '..code, Fullname = name .. ' ('..side:sub(1, 1):upper() .. side:sub(2, -1)..')', Side = side, Type = _type, Wireless = isWireless})
+ end
+ end
+ end
+ return peripherals
+ end,
+
+ PresentNamed = function(name)
+ return peripheral.isPresent(name)
+ end,
+
+ CallType = function(type, ...)
+ local tArgs = {...}
+ local p = GetPeripheral(type)
+ return peripheral.call(p.Side, unpack(tArgs))
+ end,
+
+ CallNamed = function(name, ...)
+ local tArgs = {...}
+ return peripheral.call(name, unpack(tArgs))
+ end
+}
+
+TextLine = {
+ Text = "",
+ Alignment = AlignmentLeft,
+
+ Initialise = function(self, text, alignment)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.Text = text
+ new.Alignment = alignment or AlignmentLeft
+ return new
+ end
+}
+
+local StripColours = function(str)
+ return str:gsub('['..string.char(14)..'-'..string.char(29)..']','')
+end
+
+Printer = {
+ Name = nil,
+ PeripheralType = 'printer',
+
+ paperLevel = function(self)
+ return Peripheral.CallNamed(self.Name, 'getPaperLevel')
+ end,
+
+ newPage = function(self)
+ return Peripheral.CallNamed(self.Name, 'newPage')
+ end,
+
+ endPage = function(self)
+ return Peripheral.CallNamed(self.Name, 'endPage')
+ end,
+
+ pageWrite = function(self, text)
+ return Peripheral.CallNamed(self.Name, 'write', text)
+ end,
+
+ setPageTitle = function(self, title)
+ return Peripheral.CallNamed(self.Name, 'setPageTitle', title)
+ end,
+
+ inkLevel = function(self)
+ return Peripheral.CallNamed(self.Name, 'getInkLevel')
+ end,
+
+ getCursorPos = function(self)
+ return Peripheral.CallNamed(self.Name, 'getCursorPos')
+ end,
+
+ setCursorPos = function(self, x, y)
+ return Peripheral.CallNamed(self.Name, 'setCursorPos', x, y)
+ end,
+
+ pageSize = function(self)
+ return Peripheral.CallNamed(self.Name, 'getPageSize')
+ end,
+
+ Present = function()
+ if Peripheral.GetPeripheral(Printer.PeripheralType) == nil then
+ return false
+ else
+ return true
+ end
+ end,
+
+ PrintLines = function(self, lines, title, copies)
+ local pages = {}
+ local pageLines = {}
+ for i, line in ipairs(lines) do
+ table.insert(pageLines, TextLine:Initialise(StripColours(line)))
+ if i % 25 == 0 then
+ table.insert(pages, pageLines)
+ pageLines = {}
+ end
+ end
+ if #pageLines ~= 0 then
+ table.insert(pages, pageLines)
+ end
+ return self:PrintPages(pages, title, copies)
+ end,
+
+ PrintPages = function(self, pages, title, copies)
+ copies = copies or 1
+ for c = 1, copies do
+ for p, page in ipairs(pages) do
+ if self:paperLevel() < #pages * copies then
+ return 'Add more paper to the printer'
+ end
+ if self:inkLevel() < #pages * copies then
+ return 'Add more ink to the printer'
+ end
+ self:newPage()
+ for i, line in ipairs(page) do
+ self:setCursorPos(1, i)
+ self:pageWrite(StripColours(line.Text))
+ end
+ if title then
+ self:setPageTitle(title)
+ end
+ self:endPage()
+ end
+ end
+ end,
+
+ Initialise = function(self, name)
+ if Printer.Present() then --fix
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ if name and Peripheral.PresentNamed(name) then
+ new.Name = name
+ else
+ new.Name = Peripheral.GetPeripheral(Printer.PeripheralType).Side
+ end
+ return new
+ end
+ end
+}
+
+Clipboard = {
+ Content = nil,
+ Type = nil,
+ IsCut = false,
+
+ Empty = function()
+ Clipboard.Content = nil
+ Clipboard.Type = nil
+ Clipboard.IsCut = false
+ end,
+
+ isEmpty = function()
+ return Clipboard.Content == nil
+ end,
+
+ Copy = function(content, _type)
+ Clipboard.Content = content
+ Clipboard.Type = _type or 'generic'
+ Clipboard.IsCut = false
+ end,
+
+ Cut = function(content, _type)
+ Clipboard.Content = content
+ Clipboard.Type = _type or 'generic'
+ Clipboard.IsCut = true
+ end,
+
+ Paste = function()
+ local c, t = Clipboard.Content, Clipboard.Type
+ if Clipboard.IsCut then
+ Clipboard.Empty()
+ end
+ return c, t
+ end
+}
+
+if OneOS and OneOS.Clipboard then
+ Clipboard = OneOS.Clipboard
+end
+
+Drawing = {
+
+ Screen = {
+ Width = _w,
+ Height = _h
+ },
+
+ DrawCharacters = function (x, y, characters, textColour,bgColour)
+ Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour)
+ end,
+
+ DrawBlankArea = function (x, y, w, h, colour)
+ Drawing.DrawArea (x, y, w, h, " ", 1, colour)
+ end,
+
+ DrawArea = function (x, y, w, h, character, textColour, bgColour)
+ --width must be greater than 1, other wise we get a stack overflow
+ if w < 0 then
+ w = w * -1
+ elseif w == 0 then
+ w = 1
+ end
+
+ for ix = 1, w do
+ local currX = x + ix - 1
+ for iy = 1, h do
+ local currY = y + iy - 1
+ Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour)
+ end
+ end
+ end,
+
+ DrawImage = function(_x,_y,tImage, w, h)
+ if tImage then
+ for y = 1, h do
+ if not tImage[y] then
+ break
+ end
+ for x = 1, w do
+ if not tImage[y][x] then
+ break
+ end
+ local bgColour = tImage[y][x]
+ local textColour = tImage.textcol[y][x] or colours.white
+ local char = tImage.text[y][x]
+ Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour)
+ end
+ end
+ elseif w and h then
+ Drawing.DrawBlankArea(x, y, w, h, colours.green)
+ end
+ end,
+ --using .nft
+ LoadImage = function(path)
+ local image = {
+ text = {},
+ textcol = {}
+ }
+ local fs = fs
+ if OneOS then
+ fs = OneOS.FS
+ end
+ if fs.exists(path) then
+ local _open = io.open
+ if OneOS then
+ _open = OneOS.IO.open
+ end
+ local file = _open(path, "r")
+ local sLine = file:read()
+ local num = 1
+ while sLine do
+ table.insert(image, num, {})
+ table.insert(image.text, num, {})
+ table.insert(image.textcol, num, {})
+
+ --As we're no longer 1-1, we keep track of what index to write to
+ local writeIndex = 1
+ --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour
+ local bgNext, fgNext = false, false
+ --The current background and foreground colours
+ local currBG, currFG = nil,nil
+ for i=1,#sLine do
+ local nextChar = string.sub(sLine, i, i)
+ if nextChar:byte() == 30 then
+ bgNext = true
+ elseif nextChar:byte() == 31 then
+ fgNext = true
+ elseif bgNext then
+ currBG = Drawing.GetColour(nextChar)
+ bgNext = false
+ elseif fgNext then
+ currFG = Drawing.GetColour(nextChar)
+ fgNext = false
+ else
+ if nextChar ~= " " and currFG == nil then
+ currFG = colours.white
+ end
+ image[num][writeIndex] = currBG
+ image.textcol[num][writeIndex] = currFG
+ image.text[num][writeIndex] = nextChar
+ writeIndex = writeIndex + 1
+ end
+ end
+ num = num+1
+ sLine = file:read()
+ end
+ file:close()
+ end
+ return image
+ end,
+
+ DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour)
+ w = w or Drawing.Screen.Width
+ h = h or Drawing.Screen.Height
+ x = x or 0
+ y = y or 0
+ x = math.ceil((w - #characters) / 2) + x
+ y = math.floor(h / 2) + y
+
+ Drawing.DrawCharacters(x, y, characters, textColour, bgColour)
+ end,
+
+ GetColour = function(hex)
+ if hex == ' ' then
+ return colours.transparent
+ end
+ local value = tonumber(hex, 16)
+ if not value then return nil end
+ value = math.pow(2,value)
+ return value
+ end,
+
+ Clear = function (_colour)
+ _colour = _colour or colours.black
+ Drawing.ClearBuffer()
+ Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour)
+ end,
+
+ Buffer = {},
+ BackBuffer = {},
+
+ DrawBuffer = function()
+ for y,row in pairs(Drawing.Buffer) do
+ for x,pixel in pairs(row) do
+ local shouldDraw = true
+ local hasBackBuffer = true
+ if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then
+ hasBackBuffer = false
+ end
+ 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
+ shouldDraw = false
+ end
+ if shouldDraw then
+ term.setBackgroundColour(pixel[3])
+ term.setTextColour(pixel[2])
+ term.setCursorPos(x, y)
+ term.write(pixel[1])
+ end
+ end
+ end
+ Drawing.BackBuffer = Drawing.Buffer
+ Drawing.Buffer = {}
+ term.setCursorPos(1,1)
+ end,
+
+ ClearBuffer = function()
+ Drawing.Buffer = {}
+ end,
+
+ WriteStringToBuffer = function (x, y, characters, textColour,bgColour)
+ for i = 1, #characters do
+ local character = characters:sub(i,i)
+ Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour)
+ end
+ end,
+
+ WriteToBuffer = function(x, y, character, textColour,bgColour)
+ x = round(x)
+ y = round(y)
+ if bgColour == colours.transparent then
+ Drawing.Buffer[y] = Drawing.Buffer[y] or {}
+ Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black}
+ Drawing.Buffer[y][x][1] = character
+ Drawing.Buffer[y][x][2] = textColour
+ else
+ Drawing.Buffer[y] = Drawing.Buffer[y] or {}
+ Drawing.Buffer[y][x] = {character, textColour, bgColour}
+ end
+ end,
+}
+
+Current = {
+ Document = nil,
+ TextInput = nil,
+ CursorPos = {1,1},
+ CursorColour = colours.black,
+ Selection = {8, 36},
+ Window = nil,
+ Modified = false,
+}
+
+local isQuitting = false
+
+function OrderSelection()
+ if Current.Selection then
+ if Current.Selection[1] <= Current.Selection[2] then
+ return Current.Selection
+ else
+ return {Current.Selection[2], Current.Selection[1]}
+ end
+ end
+end
+
+function StripColours(str)
+ return str:gsub('['..string.char(14)..'-'..string.char(29)..']','')
+end
+
+function FindColours(str)
+ local _, count = str:gsub('['..string.char(14)..'-'..string.char(29)..']','')
+ return count
+end
+
+ColourFromCharacter = function(character)
+ local n = character:byte() - 14
+ if n > 16 then
+ return nil
+ else
+ return 2^n
+ end
+end
+
+CharacterFromColour = function(colour)
+ return string.char(math.floor(math.log(colour)/math.log(2))+14)
+end
+
+Events = {}
+
+Button = {
+ X = 1,
+ Y = 1,
+ Width = 0,
+ Height = 0,
+ BackgroundColour = colours.lightGrey,
+ TextColour = colours.white,
+ ActiveBackgroundColour = colours.lightGrey,
+ Text = "",
+ Parent = nil,
+ _Click = nil,
+ Toggle = nil,
+
+ AbsolutePosition = function(self)
+ return self.Parent:AbsolutePosition()
+ end,
+
+ Draw = function(self)
+ local bg = self.BackgroundColour
+ local tc = self.TextColour
+ if type(bg) == 'function' then
+ bg = bg()
+ end
+
+ if self.Toggle then
+ tc = UIColours.MenuBarActive
+ bg = self.ActiveBackgroundColour
+ end
+
+ local pos = GetAbsolutePosition(self)
+ Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, bg)
+ Drawing.DrawCharactersCenter(pos.X, pos.Y, self.Width, self.Height, self.Text, tc, bg)
+ end,
+
+ Initialise = function(self, x, y, width, height, backgroundColour, parent, click, text, textColour, toggle, activeBackgroundColour)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ height = height or 1
+ new.Width = width or #text + 2
+ new.Height = height
+ new.Y = y
+ new.X = x
+ new.Text = text or ""
+ new.BackgroundColour = backgroundColour or colours.lightGrey
+ new.TextColour = textColour or colours.white
+ new.ActiveBackgroundColour = activeBackgroundColour or colours.lightGrey
+ new.Parent = parent
+ new._Click = click
+ new.Toggle = toggle
+ return new
+ end,
+
+ Click = function(self, side, x, y)
+ if self._Click then
+ if self:_Click(side, x, y, not self.Toggle) ~= false and self.Toggle ~= nil then
+ self.Toggle = not self.Toggle
+ Draw()
+ end
+ return true
+ else
+ return false
+ end
+ end
+}
+
+TextBox = {
+ X = 1,
+ Y = 1,
+ Width = 0,
+ Height = 0,
+ BackgroundColour = colours.lightGrey,
+ TextColour = colours.black,
+ Parent = nil,
+ TextInput = nil,
+ Placeholder = '',
+
+ AbsolutePosition = function(self)
+ return self.Parent:AbsolutePosition()
+ end,
+
+ Draw = function(self)
+ local pos = GetAbsolutePosition(self)
+ Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour)
+ local text = self.TextInput.Value
+ if #tostring(text) > (self.Width - 2) then
+ text = text:sub(#text-(self.Width - 3))
+ if Current.TextInput == self.TextInput then
+ Current.CursorPos = {pos.X + 1 + self.Width-2, pos.Y}
+ end
+ else
+ if Current.TextInput == self.TextInput then
+ Current.CursorPos = {pos.X + 1 + self.TextInput.CursorPos, pos.Y}
+ end
+ end
+
+ if #tostring(text) == 0 then
+ Drawing.DrawCharacters(pos.X + 1, pos.Y, self.Placeholder, colours.lightGrey, self.BackgroundColour)
+ else
+ Drawing.DrawCharacters(pos.X + 1, pos.Y, text, self.TextColour, self.BackgroundColour)
+ end
+
+ term.setCursorBlink(true)
+
+ Current.CursorColour = self.TextColour
+ end,
+
+ Initialise = function(self, x, y, width, height, parent, text, backgroundColour, textColour, done, numerical)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ height = height or 1
+ new.Width = width or #text + 2
+ new.Height = height
+ new.Y = y
+ new.X = x
+ new.TextInput = TextInput:Initialise(text or '', function(key)
+ if done then
+ done(key)
+ end
+ Draw()
+ end, numerical)
+ new.BackgroundColour = backgroundColour or colours.lightGrey
+ new.TextColour = textColour or colours.black
+ new.Parent = parent
+ return new
+ end,
+
+ Click = function(self, side, x, y)
+ Current.Input = self.TextInput
+ self:Draw()
+ end
+}
+
+TextInput = {
+ Value = "",
+ Change = nil,
+ CursorPos = nil,
+ Numerical = false,
+ IsDocument = nil,
+
+ Initialise = function(self, value, change, numerical, isDocument)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.Value = tostring(value)
+ new.Change = change
+ new.CursorPos = #tostring(value)
+ new.Numerical = numerical
+ new.IsDocument = isDocument or false
+ return new
+ end,
+
+ Insert = function(self, str)
+ if self.Numerical then
+ str = tostring(tonumber(str))
+ end
+
+ local selection = OrderSelection()
+
+ if self.IsDocument and selection then
+ self.Value = string.sub(self.Value, 1, selection[1]-1) .. str .. string.sub( self.Value, selection[2]+2)
+ self.CursorPos = selection[1]
+ Current.Selection = nil
+ else
+ local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','')
+
+ self.Value = string.sub(self.Value, 1, self.CursorPos + newLineAdjust) .. str .. string.sub( self.Value, self.CursorPos + 1 + newLineAdjust)
+ self.CursorPos = self.CursorPos + 1
+ end
+
+ self.Change(key)
+ end,
+
+ Extract = function(self, remove)
+ local selection = OrderSelection()
+ if self.IsDocument and selection then
+ local _, newLineAdjust = string.gsub(self.Value:sub(selection[1], selection[2]), '\n','')
+ local str = string.sub(self.Value, selection[1], selection[2]+1+newLineAdjust)
+ if remove then
+ self.Value = string.sub(self.Value, 1, selection[1]-1) .. string.sub( self.Value, selection[2]+2+newLineAdjust)
+ self.CursorPos = selection[1] - 1
+ Current.Selection = nil
+ end
+ return str
+ end
+ end,
+
+ Char = function(self, char)
+ if char == 'nil' then
+ return
+ end
+ self:Insert(char)
+ end,
+
+ Key = function(self, key)
+ if key == keys.enter then
+ if self.IsDocument then
+ self.Value = string.sub(self.Value, 1, self.CursorPos ) .. '\n' .. string.sub( self.Value, self.CursorPos + 1 )
+ self.CursorPos = self.CursorPos + 1
+ end
+ self.Change(key)
+ elseif key == keys.left then
+ -- Left
+ if self.CursorPos > 0 then
+ local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos))
+ self.CursorPos = self.CursorPos - 1 - colShift
+ self.Change(key)
+ end
+
+ elseif key == keys.right then
+ -- Right
+ if self.CursorPos < string.len(self.Value) then
+ local colShift = FindColours(string.sub( self.Value, self.CursorPos+1, self.CursorPos+1))
+ self.CursorPos = self.CursorPos + 1 + colShift
+ self.Change(key)
+ end
+
+ elseif key == keys.backspace then
+ -- Backspace
+ if self.IsDocument and Current.Selection then
+ self:Extract(true)
+ self.Change(key)
+ elseif self.CursorPos > 0 then
+ local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos))
+ local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','')
+
+ self.Value = string.sub( self.Value, 1, self.CursorPos - 1 - colShift + newLineAdjust) .. string.sub( self.Value, self.CursorPos + 1 - colShift + newLineAdjust)
+ self.CursorPos = self.CursorPos - 1 - colShift
+ self.Change(key)
+ end
+ elseif key == keys.home then
+ -- Home
+ self.CursorPos = 0
+ self.Change(key)
+ elseif key == keys.delete then
+ if self.IsDocument and Current.Selection then
+ self:Extract(true)
+ self.Change(key)
+ elseif self.CursorPos < string.len(self.Value) then
+ self.Value = string.sub( self.Value, 1, self.CursorPos ) .. string.sub( self.Value, self.CursorPos + 2 )
+ self.Change(key)
+ end
+ elseif key == keys["end"] then
+ -- End
+ self.CursorPos = string.len(self.Value)
+ self.Change(key)
+ elseif key == keys.up and self.IsDocument then
+ -- Up
+ if Current.Document.CursorPos then
+ local page = Current.Document.Pages[Current.Document.CursorPos.Page]
+ self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY - 1 + Current.Document.ScrollBar.Scroll, true)
+ self.Change(key)
+ end
+ elseif key == keys.down and self.IsDocument then
+ -- Down
+ if Current.Document.CursorPos then
+ local page = Current.Document.Pages[Current.Document.CursorPos.Page]
+ self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY + 1 + Current.Document.ScrollBar.Scroll, true)
+ self.Change(key)
+ end
+ end
+ end
+}
+
+Menu = {
+ X = 0,
+ Y = 0,
+ Width = 0,
+ Height = 0,
+ Owner = nil,
+ Items = {},
+ RemoveTop = false,
+
+ Draw = function(self)
+ Drawing.DrawBlankArea(self.X + 1, self.Y + 1, self.Width, self.Height, UIColours.Shadow)
+ if not self.RemoveTop then
+ Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground)
+ for i, item in ipairs(self.Items) do
+ if item.Separator then
+ Drawing.DrawArea(self.X, self.Y + i, self.Width, 1, '-', colours.grey, UIColours.MenuBackground)
+ else
+ local textColour = item.Colour or UIColours.MenuText
+ if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then
+ textColour = UIColours.MenuDisabledText
+ end
+ Drawing.DrawCharacters(self.X + 1, self.Y + i, item.Title, textColour, UIColours.MenuBackground)
+ end
+ end
+ else
+ Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground)
+ for i, item in ipairs(self.Items) do
+ if item.Separator then
+ Drawing.DrawArea(self.X, self.Y + i - 1, self.Width, 1, '-', colours.grey, UIColours.MenuBackground)
+ else
+ local textColour = item.Colour or UIColours.MenuText
+ if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then
+ textColour = UIColours.MenuDisabledText
+ end
+ Drawing.DrawCharacters(self.X + 1, self.Y + i - 1, item.Title, textColour, UIColours.MenuBackground)
+
+ Drawing.DrawCharacters(self.X - 1 + self.Width-#item.KeyName, self.Y + i - 1, item.KeyName, textColour, UIColours.MenuBackground)
+ end
+ end
+ end
+ end,
+
+ NameForKey = function(self, key)
+ if key == keys.leftCtrl then
+ return '^'
+ elseif key == keys.tab then
+ return 'Tab'
+ elseif key == keys.delete then
+ return 'Delete'
+ elseif key == keys.n then
+ return 'N'
+ elseif key == keys.a then
+ return 'A'
+ elseif key == keys.s then
+ return 'S'
+ elseif key == keys.o then
+ return 'O'
+ elseif key == keys.z then
+ return 'Z'
+ elseif key == keys.y then
+ return 'Y'
+ elseif key == keys.c then
+ return 'C'
+ elseif key == keys.x then
+ return 'X'
+ elseif key == keys.v then
+ return 'V'
+ elseif key == keys.r then
+ return 'R'
+ elseif key == keys.l then
+ return 'L'
+ elseif key == keys.t then
+ return 'T'
+ elseif key == keys.h then
+ return 'H'
+ elseif key == keys.e then
+ return 'E'
+ elseif key == keys.p then
+ return 'P'
+ elseif key == keys.f then
+ return 'F'
+ elseif key == keys.m then
+ return 'M'
+ elseif key == keys.q then
+ return 'Q'
+ else
+ return '?'
+ end
+ end,
+
+ Initialise = function(self, x, y, items, owner, removeTop)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ if not owner then
+ return
+ end
+
+ local keyNames = {}
+
+ for i, v in ipairs(items) do
+ items[i].KeyName = ''
+ if v.Keys then
+ for _i, key in ipairs(v.Keys) do
+ items[i].KeyName = items[i].KeyName .. self:NameForKey(key)
+ end
+ end
+ if items[i].KeyName ~= '' then
+ table.insert(keyNames, items[i].KeyName)
+ end
+ end
+ local keysLength = LongestString(keyNames)
+ if keysLength > 0 then
+ keysLength = keysLength + 2
+ end
+
+ new.Width = LongestString(items, 'Title') + 2 + keysLength
+ if new.Width < 10 then
+ new.Width = 10
+ end
+ new.Height = #items + 2
+ new.RemoveTop = removeTop or false
+ if removeTop then
+ new.Height = new.Height - 1
+ end
+
+ if y < 1 then
+ y = 1
+ end
+ if x < 1 then
+ x = 1
+ end
+
+ if y + new.Height > Drawing.Screen.Height + 1 then
+ y = Drawing.Screen.Height - new.Height
+ end
+ if x + new.Width > Drawing.Screen.Width + 1 then
+ x = Drawing.Screen.Width - new.Width
+ end
+
+
+ new.Y = y
+ new.X = x
+ new.Items = items
+ new.Owner = owner
+ return new
+ end,
+
+ New = function(self, x, y, items, owner, removeTop)
+ if Current.Menu and Current.Menu.Owner == owner then
+ Current.Menu = nil
+ return
+ end
+
+ local new = self:Initialise(x, y, items, owner, removeTop)
+ Current.Menu = new
+ return new
+ end,
+
+ Click = function(self, side, x, y)
+ local i = y-1
+ if self.RemoveTop then
+ i = y
+ end
+ if i >= 1 and y < self.Height then
+ if not ((self.Items[i].Enabled and type(self.Items[i].Enabled) == 'function' and self.Items[i].Enabled() == false) or self.Items[i].Enabled == false) and self.Items[i].Click then
+ self.Items[i]:Click()
+ if Current.Menu.Owner and Current.Menu.Owner.Toggle then
+ Current.Menu.Owner.Toggle = false
+ end
+ Current.Menu = nil
+ self = nil
+ end
+ return true
+ end
+ end
+}
+
+MenuBar = {
+ X = 1,
+ Y = 1,
+ Width = Drawing.Screen.Width,
+ Height = 1,
+ MenuBarItems = {},
+
+ AbsolutePosition = function(self)
+ return {X = self.X, Y = self.Y}
+ end,
+
+ Draw = function(self)
+ --Drawing.DrawArea(self.X - 1, self.Y, 1, self.Height, "|", UIColours.ToolbarText, UIColours.Background)
+
+ Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, colours.grey)
+ for i, button in ipairs(self.MenuBarItems) do
+ button:Draw()
+ end
+ end,
+
+ Initialise = function(self, items)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.X = 1
+ new.Y = 1
+ new.MenuBarItems = items
+ return new
+ end,
+
+ AddToolbarItem = function(self, item)
+ table.insert(self.ToolbarItems, item)
+ self:CalculateToolbarItemPositions()
+ end,
+
+ CalculateToolbarItemPositions = function(self)
+ local currY = 1
+ for i, toolbarItem in ipairs(self.ToolbarItems) do
+ toolbarItem.Y = currY
+ currY = currY + toolbarItem.Height
+ end
+ end,
+
+ Click = function(self, side, x, y)
+ for i, item in ipairs(self.MenuBarItems) do
+ if item.X <= x and item.X + item.Width > x then
+ if item:Click(item, side, x - item.X + 1, 1) then
+ return true
+ end
+ end
+ end
+ return false
+ end
+}
+
+TextFormatPlainText = 1
+TextFormatInkText = 2
+
+Document = {
+ X = 1,
+ Y = 1,
+ PageSize = {Width = 25, Height = 21},
+ TextInput = nil,
+ Pages = {},
+ Format = TextFormatPlainText,
+ Title = '',
+ Path = nil,
+ ScrollBar = nil,
+ Lines = {},
+ CursorPos = nil,
+
+ CalculateLineWrapping = function(self)
+ local limit = self.PageSize.Width
+ local text = self.TextInput.Value
+ local lines = {''}
+ local words = {}
+
+ for word, space in text:gmatch('(%S+)(%s*)') do
+ for i = 1, math.ceil(#word/limit) do
+ local _space = ''
+ if i == math.ceil(#word/limit) then
+ _space = space
+ end
+ table.insert(words, {word:sub(1+limit*(i-1), limit*i), _space})
+ end
+ end
+
+ for i, ws in ipairs(words) do
+ local word = ws[1]
+ local space = ws[2]
+ local temp = lines[#lines] .. word .. space:gsub('\n','')
+ if #temp > limit then
+ table.insert(lines, '')
+ end
+ if space:find('\n') then
+ lines[#lines] = lines[#lines] .. word
+
+ space = space:gsub('\n', function()
+ table.insert(lines, '')
+ return ''
+ end)
+ else
+ lines[#lines] = lines[#lines] .. word .. space
+ end
+ end
+ return lines
+ end,
+
+ CalculateCursorPos = function(self)
+ local passedCharacters = 0
+ Current.CursorPos = nil
+ for p, page in ipairs(self.Pages) do
+ page:Draw()
+ if not Current.CursorPos then
+ for i, line in ipairs(page.Lines) do
+ local relCursor = self.TextInput.CursorPos - FindColours(self.TextInput.Value:sub(1,self.TextInput.CursorPos))
+ if passedCharacters + #StripColours(line.Text:gsub('\n','')) >= relCursor then
+ Current.CursorPos = {self.X + page.MarginX + (relCursor - passedCharacters), page.Y + 1 + i}
+ self.CursorPos = {Page = p, Line = i, Collum = relCursor - passedCharacters - FindColours(self.TextInput.Value:sub(1,self.TextInput.CursorPos-1))}
+ break
+ end
+ passedCharacters = passedCharacters + #StripColours(line.Text:gsub('\n',''))
+ end
+ end
+ end
+ end,
+
+ Draw = function(self)
+ self:CalculatePages()
+ self:CalculateCursorPos()
+ self.ScrollBar:Draw()
+ end,
+
+ CalculatePages = function(self)
+ self.Pages = {}
+ local lines = self:CalculateLineWrapping()
+ self.Lines = lines
+ local pageLines = {}
+ local totalPageHeight = (3 + self.PageSize.Height + 2 * Page.MarginY)
+ for i, line in ipairs(lines) do
+ table.insert(pageLines, TextLine:Initialise(line))
+ if i % self.PageSize.Height == 0 then
+ table.insert(self.Pages, Page:Initialise(self, pageLines, 3 - self.ScrollBar.Scroll + totalPageHeight*(#self.Pages)))
+ pageLines = {}
+ end
+ end
+ if #pageLines ~= 0 then
+ table.insert(self.Pages, Page:Initialise(self, pageLines, 3 - self.ScrollBar.Scroll + totalPageHeight*(#self.Pages)))
+ end
+
+ self.ScrollBar.MaxScroll = totalPageHeight*(#self.Pages) - Drawing.Screen.Height + 1
+ end,
+
+ ScrollToCursor = function(self)
+ self:CalculateCursorPos()
+ if Current.CursorPos and
+ (Current.CursorPos[2] > Drawing.Screen.Height
+ or Current.CursorPos[2] < 2) then
+ self.ScrollBar:DoScroll(Current.CursorPos[2] - Drawing.Screen.Height)
+ end
+ end,
+
+ SetSelectionColour = function(self, colour)
+ local selection = OrderSelection()
+ local text = self.TextInput:Extract(true)
+ local colChar = CharacterFromColour(colour)
+ local precedingColour = ''
+ if FindColours(self.TextInput.Value:sub(self.TextInput.CursorPos+1, self.TextInput.CursorPos+1)) == 0 then
+ for i = 1, self.TextInput.CursorPos do
+ local c = self.TextInput.Value:sub(self.TextInput.CursorPos - i,self.TextInput.CursorPos - i)
+ if FindColours(c) == 1 then
+ precedingColour = c
+ break
+ end
+ end
+ if precedingColour == '' then
+ precedingColour = CharacterFromColour(colours.black)
+ end
+ end
+
+ self.TextInput:Insert(colChar..StripColours(text)..precedingColour)
+ --text = text:gsub('['..string.char(14)..'-'..string.char(29)..']','')
+ end,
+
+ Initialise = function(self, text, title, path)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.Title = title or 'New Document'
+ new.Path = path
+ new.X = (Drawing.Screen.Width - (new.PageSize.Width + 2*(Page.MarginX)))/2
+ new.Y = 2
+ new.TextInput = TextInput:Initialise(text, function()
+ new:ScrollToCursor()
+ Current.Modified = true
+ Draw()
+ end, false, true)
+ new.ScrollBar = ScrollBar:Initialise(Drawing.Screen.Width, new.Y, Drawing.Screen.Height-1, 0, nil, nil, nil, function()end)
+ Current.TextInput = new.TextInput
+ Current.ScrollBar = new.ScrollBar
+ return new
+ end
+}
+
+ScrollBar = {
+ X = 1,
+ Y = 1,
+ Width = 1,
+ Height = 1,
+ BackgroundColour = colours.grey,
+ BarColour = colours.lightBlue,
+ Parent = nil,
+ Change = nil,
+ Scroll = 0,
+ MaxScroll = 0,
+ ClickPoint = nil,
+
+ AbsolutePosition = function(self)
+ return self.Parent:AbsolutePosition()
+ end,
+
+ Draw = function(self)
+ local pos = GetAbsolutePosition(self)
+ local barHeight = self.Height - self.MaxScroll
+ if barHeight < 3 then
+ barHeight = 3
+ end
+ local percentage = (self.Scroll/self.MaxScroll)
+
+ Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour)
+ Drawing.DrawBlankArea(pos.X, pos.Y + round(self.Height*percentage - barHeight*percentage), self.Width, barHeight, self.BarColour)
+ end,
+
+ Initialise = function(self, x, y, height, maxScroll, backgroundColour, barColour, parent, change)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.Width = 1
+ new.Height = height
+ new.Y = y
+ new.X = x
+ new.BackgroundColour = backgroundColour or colours.grey
+ new.BarColour = barColour or colours.lightBlue
+ new.Parent = parent
+ new.Change = change or function()end
+ new.MaxScroll = maxScroll
+ new.Scroll = 0
+ return new
+ end,
+
+ DoScroll = function(self, amount)
+ amount = round(amount)
+ if self.Scroll < 0 or self.Scroll > self.MaxScroll then
+ return false
+ end
+ self.Scroll = self.Scroll + amount
+ if self.Scroll < 0 then
+ self.Scroll = 0
+ elseif self.Scroll > self.MaxScroll then
+ self.Scroll = self.MaxScroll
+ end
+ self.Change()
+ return true
+ end,
+
+ Click = function(self, side, x, y, drag)
+ local percentage = (self.Scroll/self.MaxScroll)
+ local barHeight = (self.Height - self.MaxScroll)
+ if barHeight < 3 then
+ barHeight = 3
+ end
+ local relScroll = (self.MaxScroll*(y + barHeight*percentage)/self.Height)
+ if not drag then
+ self.ClickPoint = self.Scroll - relScroll + 1
+ end
+
+ if self.Scroll-1 ~= relScroll then
+ self:DoScroll(relScroll-self.Scroll-1 + self.ClickPoint)
+ end
+ return true
+ end
+}
+
+AlignmentLeft = 1
+AlignmentCentre = 2
+AlignmentRight = 3
+
+TextLine = {
+ Text = "",
+ Alignment = AlignmentLeft,
+
+ Initialise = function(self, text, alignment)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.Text = text
+ new.Alignment = alignment or AlignmentLeft
+ return new
+ end
+}
+
+local clickPos = 1
+Page = {
+ X = 1,
+ Y = 1,
+ Width = 1,
+ Height = 1,
+ MarginX = 3,
+ MarginY = 2,
+ BackgroundColour = colours.white,
+ TextColour = colours.white,
+ ActiveBackgroundColour = colours.lightGrey,
+ Lines = {},
+ Parent = nil,
+
+ AbsolutePosition = function(self)
+ return self.Parent:AbsolutePosition()
+ end,
+
+ Draw = function(self)
+ local pos = GetAbsolutePosition(self)
+
+ if pos.Y > Drawing.Screen.Height or pos.Y + self.Height < 1 then
+ return
+ end
+
+ Drawing.DrawBlankArea(pos.X+self.Width,pos.Y -1 + 1, 1, self.Height, UIColours.Shadow)
+ Drawing.DrawBlankArea(pos.X+1, pos.Y -1 + self.Height, self.Width, 1, UIColours.Shadow)
+ Drawing.DrawBlankArea(pos.X, pos.Y -1, self.Width, self.Height, self.BackgroundColour)
+
+ local textColour = self.TextColour
+ if not Current.Selection then
+ for i, line in ipairs(self.Lines) do
+ local _c = 1
+ for c = 1, #line.Text do
+ local col = ColourFromCharacter(line.Text:sub(c,c))
+ if col then
+ textColour = col
+ else
+ Drawing.WriteToBuffer(pos.X + self.MarginX - 1 + _c, pos.Y -2 + i + self.MarginY, line.Text:sub(c,c), textColour, self.BackgroundColour)
+ _c = _c + 1
+ end
+ end
+ end
+ else
+ local selection = OrderSelection()
+ local char = 1
+ local textColour = self.TextColour
+ for i, line in ipairs(self.Lines) do
+ local _c = 1
+ for c = 1, #line.Text do
+ local col = ColourFromCharacter(line.Text:sub(c,c))
+ if col then
+ textColour = col
+ else
+ local tc = textColour
+ local colour = colours.white
+ if char >= selection[1] and char <= selection[2] then
+ colour = colours.lightBlue
+ tc = colours.white
+ end
+
+ Drawing.WriteToBuffer(pos.X + self.MarginX - 1 + _c, pos.Y -2 + i + self.MarginY, line.Text:sub(c,c), tc, colour)
+ _c = _c + 1
+ end
+ char = char + 1
+ end
+ end
+ end
+ end,
+
+ Initialise = function(self, parent, lines, y)
+ local new = {} -- the new instanc
+ setmetatable( new, {__index = self} )
+ new.Height = parent.PageSize.Height + 2 * self.MarginY
+ new.Width = parent.PageSize.Width + 2 * self.MarginX
+ new.X = 1
+ new.Y = y or 1
+ new.Lines = lines or {}
+ new.BackgroundColour = colours.white
+ new.TextColour = colours.black
+ new.Parent = parent
+ new.ClickPos = 1
+ return new
+ end,
+
+ GetCursorPosFromPoint = function(self, x, y, rel)
+ local pos = GetAbsolutePosition(self)
+ if rel then
+ pos = {Y = 0, X = 0}
+ end
+ local row = y - pos.Y + self.MarginY - self.Parent.ScrollBar.Scroll
+ local col = x - self.MarginX - pos.X + 1
+ local cursorPos = 0
+ if row <= 0 or col <= 0 then
+ return 0
+ end
+
+ if row > #self.Lines then
+ for i, v in ipairs(self.Lines) do
+ cursorPos = cursorPos + #v.Text-- - FindColours(v.Text)
+ end
+ return cursorPos
+ end
+
+ --term.setCursorPos(1,3)
+ local prevLineCount = 0
+ for i, v in ipairs(self.Lines) do
+ if i == row then
+ if col > #v.Text then
+ col = #v.Text-- + FindColours(v.Text)
+ else
+ col = col + FindColours(v.Text:sub(1, col))
+ end
+ --term.setCursorPos(1,2)
+ --print(prevLineCount)
+ cursorPos = cursorPos + col + 2 - i - prevLineCount
+ break
+ else
+ prevLineCount = FindColours(v.Text)
+ if prevLineCount ~= 0 then
+ prevLineCount = prevLineCount
+ end
+ cursorPos = cursorPos + #v.Text + 2 - i + FindColours(v.Text)
+ end
+ end
+
+ return cursorPos - 2
+ end,
+
+ Click = function(self, side, x, y, drag)
+ local cursorPos = self:GetCursorPosFromPoint(x, y)
+ self.Parent.TextInput.CursorPos = cursorPos
+ if drag == nil then
+ Current.Selection = nil
+ clickPos = x
+ else
+ local relCursor = cursorPos-- - FindColours(self.Parent.TextInput.Value:sub(1,cursorPos)) + 1
+ if not Current.Selection then
+ local adder = 1
+ if clickPos and clickPos < x then
+ adder = 0
+ end
+ Current.Selection = {relCursor + adder, relCursor + 1 + adder}
+ else
+ Current.Selection[2] = relCursor + 1
+ end
+ end
+ Draw()
+ return true
+ end
+}
+
+function GetAbsolutePosition(object)
+ local obj = object
+ local i = 0
+ local x = 1
+ local y = 1
+ while true do
+ x = x + obj.X - 1
+ y = y + obj.Y - 1
+
+ if not obj.Parent then
+ return {X = x, Y = y}
+ end
+
+ obj = obj.Parent
+
+ if i > 32 then
+ return {X = 1, Y = 1}
+ end
+
+ i = i + 1
+ end
+
+end
+
+function Draw()
+ if not Current.Window then
+ Drawing.Clear(colours.lightGrey)
+ else
+ Drawing.DrawArea(1, 2, Drawing.Screen.Width, Drawing.Screen.Height, '|', colours.black, colours.lightGrey)
+ end
+
+ if Current.Document then
+ Current.Document:Draw()
+ end
+
+ Current.MenuBar:Draw()
+
+ if Current.Window then
+ Current.Window:Draw()
+ end
+
+ if Current.Menu then
+ Current.Menu:Draw()
+ end
+
+ Drawing.DrawBuffer()
+
+ if Current.TextInput and Current.CursorPos and not Current.Menu and not(Current.Window and Current.Document and Current.TextInput == Current.Document.TextInput) and Current.CursorPos[2] > 1 then
+ term.setCursorPos(Current.CursorPos[1], Current.CursorPos[2])
+ term.setCursorBlink(true)
+ term.setTextColour(Current.CursorColour)
+ else
+ term.setCursorBlink(false)
+ end
+end
+MainDraw = Draw
+
+LongestString = function(input, key)
+ local length = 0
+ for i = 1, #input do
+ local value = input[i]
+ if key then
+ if value[key] then
+ value = value[key]
+ else
+ value = ''
+ end
+ end
+ local titleLength = string.len(value)
+ if titleLength > length then
+ length = titleLength
+ end
+ end
+ return length
+end
+
+function LoadMenuBar()
+ Current.MenuBar = MenuBar:Initialise({
+ Button:Initialise(1, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle)
+ if toggle then
+ Menu:New(1, 2, {
+ {
+ Title = "New...",
+ Click = function()
+ Current.Document = Document:Initialise('')
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.n
+ }
+ },
+ {
+ Title = 'Open...',
+ Click = function()
+ DisplayOpenDocumentWindow()
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.o
+ }
+ },
+ {
+ Separator = true
+ },
+ {
+ Title = 'Save...',
+ Click = function()
+ SaveDocument()
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.s
+ },
+ Enabled = function()
+ return true
+ end
+ },
+ {
+ Separator = true
+ },
+ {
+ Title = 'Print...',
+ Click = function()
+ PrintDocument()
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.p
+ },
+ Enabled = function()
+ return true
+ end
+ },
+ {
+ Separator = true
+ },
+ {
+ Title = 'Quit',
+ Click = function()
+ Close()
+ end
+ },
+ --[[
+ {
+ Title = 'Save As...',
+ Click = function()
+
+ end
+ }
+ ]]--
+ }, self, true)
+ else
+ Current.Menu = nil
+ end
+ return true
+ end, 'File', colours.lightGrey, false),
+ Button:Initialise(7, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle)
+ if not self.Toggle then
+ Menu:New(7, 2, {
+ --[[
+ {
+ Title = "Undo",
+ Click = function()
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.z
+ },
+ Enabled = function()
+ return false
+ end
+ },
+ {
+ Title = 'Redo',
+ Click = function()
+
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.y
+ },
+ Enabled = function()
+ return false
+ end
+ },
+ {
+ Separator = true
+ },
+ ]]--
+ {
+ Title = 'Cut',
+ Click = function()
+ Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text')
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.x
+ },
+ Enabled = function()
+ return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil
+ end
+ },
+ {
+ Title = 'Copy',
+ Click = function()
+ Clipboard.Copy(Current.Document.TextInput:Extract(), 'text')
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.c
+ },
+ Enabled = function()
+ return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil
+ end
+ },
+ {
+ Title = 'Paste',
+ Click = function()
+ local paste = Clipboard.Paste()
+ Current.Document.TextInput:Insert(paste)
+ Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.v
+ },
+ Enabled = function()
+ return Current.Document ~= nil and (not Clipboard.isEmpty()) and Clipboard.Type == 'text'
+ end
+ },
+ {
+ Separator = true,
+ },
+ {
+ Title = 'Select All',
+ Click = function()
+ Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')}
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.a
+ },
+ Enabled = function()
+ return Current.Document ~= nil
+ end
+ }
+ }, self, true)
+ else
+ Current.Menu = nil
+ end
+ return true
+ end, 'Edit', colours.lightGrey, false)
+ })
+end
+
+function LoadMenuBar()
+ Current.MenuBar = MenuBar:Initialise({
+ Button:Initialise(1, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle)
+ if toggle then
+ Menu:New(1, 2, {
+ {
+ Title = "New...",
+ Click = function()
+ Current.Document = Document:Initialise('')
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.n
+ }
+ },
+ {
+ Title = 'Open...',
+ Click = function()
+ DisplayOpenDocumentWindow()
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.o
+ }
+ },
+ {
+ Separator = true
+ },
+ {
+ Title = 'Save...',
+ Click = function()
+ SaveDocument()
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.s
+ },
+ Enabled = function()
+ return Current.Document ~= nil
+ end
+ },
+ {
+ Separator = true
+ },
+ {
+ Title = 'Print...',
+ Click = function()
+ PrintDocument()
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.p
+ },
+ Enabled = function()
+ return true
+ end
+ },
+ {
+ Separator = true
+ },
+ {
+ Title = 'Quit',
+ Click = function()
+ if Close() and OneOS then
+ OneOS.Close()
+ end
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.q
+ }
+ },
+ --[[
+ {
+ Title = 'Save As...',
+ Click = function()
+
+ end
+ }
+ ]]--
+ }, self, true)
+ else
+ Current.Menu = nil
+ end
+ return true
+ end, 'File', colours.lightGrey, false),
+ Button:Initialise(7, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle)
+ if not self.Toggle then
+ Menu:New(7, 2, {
+ --[[
+ {
+ Title = "Undo",
+ Click = function()
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.z
+ },
+ Enabled = function()
+ return false
+ end
+ },
+ {
+ Title = 'Redo',
+ Click = function()
+
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.y
+ },
+ Enabled = function()
+ return false
+ end
+ },
+ {
+ Separator = true
+ },
+ ]]--
+ {
+ Title = 'Cut',
+ Click = function()
+ Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text')
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.x
+ },
+ Enabled = function()
+ return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil
+ end
+ },
+ {
+ Title = 'Copy',
+ Click = function()
+ Clipboard.Copy(Current.Document.TextInput:Extract(), 'text')
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.c
+ },
+ Enabled = function()
+ return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil
+ end
+ },
+ {
+ Title = 'Paste',
+ Click = function()
+ local paste = Clipboard.Paste()
+ Current.Document.TextInput:Insert(paste)
+ Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.v
+ },
+ Enabled = function()
+ return Current.Document ~= nil and (not Clipboard.isEmpty()) and Clipboard.Type == 'text'
+ end
+ },
+ {
+ Separator = true,
+ },
+ {
+ Title = 'Select All',
+ Click = function()
+ Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')}
+ end,
+ Keys = {
+ keys.leftCtrl,
+ keys.a
+ },
+ Enabled = function()
+ return Current.Document ~= nil
+ end
+ }
+ }, self, true)
+ else
+ Current.Menu = nil
+ end
+ return true
+ end, 'Edit', colours.lightGrey, false),
+ Button:Initialise(13, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle)
+ if not self.Toggle then
+ Menu:New(13, 2, {
+ {
+ Title = 'Red',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.red,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Orange',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.orange,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Yellow',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.yellow,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Pink',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.pink,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Magenta',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.magenta,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Purple',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.purple,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Light Blue',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.lightBlue,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Cyan',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.cyan,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Blue',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.blue,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Green',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.green,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Light Grey',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.lightGrey,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Grey',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.grey,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Black',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.black,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ },
+ {
+ Title = 'Brown',
+ Click = function(item)
+ Current.Document:SetSelectionColour(item.Colour)
+ end,
+ Colour = colours.brown,
+ Enabled = function()
+ return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil)
+ end
+ }
+ }, self, true)
+ else
+ Current.Menu = nil
+ end
+ return true
+ end, 'Colour', colours.lightGrey, false)
+ })
+end
+
+function SplashScreen()
+ local w = colours.white
+ local b = colours.black
+ local u = colours.blue
+ local lb = colours.lightBlue
+ local splashIcon = {{w,w,w,b,w,w,w,b,w,w,w,},{w,w,w,b,w,w,w,b,w,w,w,},{w,w,w,b,u,u,u,b,w,w,w,},{w,b,b,u,u,u,u,u,b,b,w,},{b,u,u,lb,lb,u,u,u,u,u,b,},{b,u,lb,lb,u,u,u,u,u,u,b,},{b,u,lb,lb,u,u,u,u,u,u,b,},{b,u,u,u,u,u,u,u,u,u,b,},{w,b,b,b,b,b,b,b,b,b,w,},
+ ["text"]={{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," ","I","n","k"," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," ","b","y"," ","o","e","e","d"," "," "},{" "," "," "," "," "," "," "," "," "," "," ",},},
+ ["textcol"]={{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{lb,lb,lb,lb,lb,lb,lb,lb,lb,lb,lb,},{w,w,w,w,w,w,w,w,w,w,w,},},}
+ Drawing.Clear(colours.white)
+ Drawing.DrawImage((Drawing.Screen.Width - 11)/2, (Drawing.Screen.Height - 9)/2, splashIcon, 11, 9)
+ Drawing.DrawBuffer()
+ Drawing.Clear(colours.black)
+ parallel.waitForAny(function()sleep(1)end, function()os.pullEvent('mouse_click')end)
+end
+
+function Initialise(arg)
+ if OneOS then
+ fs = OneOS.FS
+ end
+
+ if not OneOS then
+ SplashScreen()
+ end
+ EventRegister('mouse_click', TryClick)
+ EventRegister('mouse_drag', function(event, side, x, y)TryClick(event, side, x, y, true)end)
+ EventRegister('mouse_scroll', Scroll)
+ EventRegister('key', HandleKey)
+ EventRegister('char', HandleKey)
+ EventRegister('timer', Timer)
+ EventRegister('terminate', function(event) if Close() then error( "Terminated", 0 ) end end)
+
+ LoadMenuBar()
+
+ --Current.Document = Document:Initialise('abcdefghijklmnopqrtuvwxy')--'Hello everybody!')
+ if tArgs[1] then
+ if fs.exists(tArgs[1]) then
+ OpenDocument(tArgs[1])
+ else
+ --new
+ end
+ else
+ Current.Document = Document:Initialise('')--'Hello everybody!')
+ end
+
+ --[[
+ if arg and fs.exists(arg) then
+ OpenDocument(arg)
+ else
+ DisplayNewDocumentWindow()
+ end
+ ]]--
+ Draw()
+
+ EventHandler()
+end
+
+local isControlPushed = false
+controlPushedTimer = nil
+closeWindowTimer = nil
+function Timer(event, timer)
+ if timer == closeWindowTimer then
+ if Current.Window then
+ Current.Window:Close()
+ end
+ Draw()
+ elseif timer == controlPushedTimer then
+ isControlPushed = false
+ end
+end
+
+local ignoreNextChar = false
+function HandleKey(...)
+ local args = {...}
+ local event = args[1]
+ local keychar = args[2]
+ --Mac left command character
+ if event == 'key' and keychar == keys.leftCtrl or keychar == keys.rightCtrl or keychar == 219 then
+ isControlPushed = true
+ controlPushedTimer = os.startTimer(0.5)
+ elseif isControlPushed then
+ if event == 'key' then
+ if CheckKeyboardShortcut(keychar) then
+ isControlPushed = false
+ ignoreNextChar = true
+ end
+ end
+ elseif ignoreNextChar then
+ ignoreNextChar = false
+ elseif Current.TextInput then
+ if event == 'char' then
+ Current.TextInput:Char(keychar)
+ elseif event == 'key' then
+ Current.TextInput:Key(keychar)
+ end
+ end
+end
+
+function CheckKeyboardShortcut(key)
+ local shortcuts = {}
+ shortcuts[keys.n] = function() Current.Document = Document:Initialise('') end
+ shortcuts[keys.o] = function() DisplayOpenDocumentWindow() end
+ shortcuts[keys.s] = function() if Current.Document ~= nil then SaveDocument() end end
+ shortcuts[keys.left] = function() if Current.TextInput then Current.TextInput:Key(keys.home) end end
+ shortcuts[keys.right] = function() if Current.TextInput then Current.TextInput:Key(keys["end"]) end end
+-- shortcuts[keys.q] = function() DisplayOpenDocumentWindow() end
+ if Current.Document ~= nil then
+ shortcuts[keys.s] = function() SaveDocument() end
+ shortcuts[keys.p] = function() PrintDocument() end
+ if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then
+ shortcuts[keys.x] = function() Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text') end
+ shortcuts[keys.c] = function() Clipboard.Copy(Current.Document.TextInput:Extract(), 'text') end
+ end
+ if (not Clipboard.isEmpty()) and Clipboard.Type == 'text' then
+ shortcuts[keys.v] = function() local paste = Clipboard.Paste()
+ Current.Document.TextInput:Insert(paste)
+ Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1
+ end
+ end
+ shortcuts[keys.a] = function() Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')} end
+ end
+
+ if shortcuts[key] then
+ shortcuts[key]()
+ Draw()
+ return true
+ else
+ return false
+ end
+end
+
+--[[
+ Check if the given object falls under the click coordinates
+]]--
+function CheckClick(object, x, y)
+ if object.X <= x and object.Y <= y and object.X + object.Width > x and object.Y + object.Height > y then
+ return true
+ end
+end
+
+--[[
+ Attempt to clicka given object
+]]--
+function DoClick(object, side, x, y, drag)
+ local obj = GetAbsolutePosition(object)
+ obj.Width = object.Width
+ obj.Height = object.Height
+ if object and CheckClick(obj, x, y) then
+ return object:Click(side, x - object.X + 1, y - object.Y + 1, drag)
+ end
+end
+
+--[[
+ Try to click at the given coordinates
+]]--
+function TryClick(event, side, x, y, drag)
+ if Current.Menu then
+ if DoClick(Current.Menu, side, x, y, drag) then
+ Draw()
+ return
+ else
+ if Current.Menu.Owner and Current.Menu.Owner.Toggle then
+ Current.Menu.Owner.Toggle = false
+ end
+ Current.Menu = nil
+ Draw()
+ return
+ end
+ elseif Current.Window then
+ if DoClick(Current.Window, side, x, y, drag) then
+ Draw()
+ return
+ else
+ Current.Window:Flash()
+ return
+ end
+ end
+ local interfaceElements = {}
+
+ table.insert(interfaceElements, Current.MenuBar)
+ table.insert(interfaceElements, Current.ScrollBar)
+ for i, page in ipairs(Current.Document.Pages) do
+ table.insert(interfaceElements, page)
+ end
+
+ for i, object in ipairs(interfaceElements) do
+ if DoClick(object, side, x, y, drag) then
+ Draw()
+ return
+ end
+ end
+ Draw()
+end
+
+function Scroll(event, direction, x, y)
+ if Current.Window and Current.Window.OpenButton then
+ Current.Document.Scroll = Current.Document.Scroll + direction
+ if Current.Window.Scroll < 0 then
+ Current.Window.Scroll = 0
+ elseif Current.Window.Scroll > Current.Window.MaxScroll then
+ Current.Window.Scroll = Current.Window.MaxScroll
+ end
+ Draw()
+ elseif Current.ScrollBar then
+ if Current.ScrollBar:DoScroll(direction*2) then
+ Draw()
+ end
+ end
+end
+
+
+--[[
+ Registers functions to run on certain events
+]]--
+function EventRegister(event, func)
+ if not Events[event] then
+ Events[event] = {}
+ end
+
+ table.insert(Events[event], func)
+end
+
+--[[
+ The main loop event handler, runs registered event functinos
+]]--
+function EventHandler()
+ while true do
+ local event, arg1, arg2, arg3, arg4 = os.pullEventRaw()
+ if Events[event] then
+ for i, e in ipairs(Events[event]) do
+ e(event, arg1, arg2, arg3, arg4)
+ end
+ end
+ end
+end
+
+
+local function Extension(path, addDot)
+ if not path then
+ return nil
+ elseif not string.find(fs.getName(path), '%.') then
+ if not addDot then
+ return fs.getName(path)
+ else
+ return ''
+ end
+ else
+ local _path = path
+ if path:sub(#path) == '/' then
+ _path = path:sub(1,#path-1)
+ end
+ local extension = _path:gmatch('%.[0-9a-z]+$')()
+ if extension then
+ extension = extension:sub(2)
+ else
+ --extension = nil
+ return ''
+ end
+ if addDot then
+ extension = '.'..extension
+ end
+ return extension:lower()
+ end
+end
+
+local RemoveExtension = function(path)
+ if path:sub(1,1) == '.' then
+ return path
+ end
+ local extension = Extension(path)
+ if extension == path then
+ return fs.getName(path)
+ end
+ return string.gsub(path, extension, ''):sub(1, -2)
+end
+
+local acknowledgedColour = false
+function PrintDocument()
+ if OneOS then
+ OneOS.LoadAPI('/System/API/Helpers.lua')
+ OneOS.LoadAPI('/System/API/Peripheral.lua')
+ OneOS.LoadAPI('/System/API/Printer.lua')
+ end
+
+ local doPrint = function()
+ local window = PrintDocumentWindow:Initialise():Show()
+ end
+
+ if Peripheral.GetPeripheral('printer') == nil then
+ ButtonDialougeWindow:Initialise('No Printer Found', 'Please place a printer next to your computer. Ensure you also insert dye (left slot) and paper (top slots)', 'Ok', nil, function(window, ok)
+ window:Close()
+ end):Show()
+ elseif not acknowledgedColour and FindColours(Current.Document.TextInput.Value) ~= 0 then
+ ButtonDialougeWindow:Initialise('Important', 'Due to the way printers work, you can\'t print in more than one colour. The dye you use will be the colour of the text.', 'Ok', nil, function(window, ok)
+ acknowledgedColour = true
+ window:Close()
+ doPrint()
+ end):Show()
+ else
+ doPrint()
+ end
+end
+
+function SaveDocument()
+ local function save()
+ local h = fs.open(Current.Document.Path, 'w')
+ if h then
+ if Current.Document.Format == TextFormatPlainText then
+ h.write(Current.Document.TextInput.Value)
+ else
+ local lines = {}
+ for p, page in ipairs(Current.Document.Pages) do
+ for i, line in ipairs(page.Lines) do
+ table.insert(lines, line)
+ end
+ end
+ h.write(textutils.serialize(lines))
+ end
+ Current.Modified = false
+ else
+ ButtonDialougeWindow:Initialise('Error', 'An error occured while saving the file, try again.', 'Ok', nil, function(window, ok)
+ window:Close()
+ end):Show()
+ end
+ h.close()
+ end
+
+ if not Current.Document.Path then
+ SaveDocumentWindow:Initialise(function(self, success, path)
+ self:Close()
+ if success then
+ local extension = ''
+ if Current.Document.Format == TextFormatPlainText then
+ extension = '.txt'
+ elseif Current.Document.Format == TextFormatInkText then
+ extension = '.ink'
+ end
+
+ if path:sub(-4) ~= extension then
+ path = path .. extension
+ end
+
+ Current.Document.Path = path
+ Current.Document.Title = fs.getName(path)
+ save()
+ end
+ if Current.Document then
+ Current.TextInput = Current.Document.TextInput
+ end
+ end):Show()
+ else
+ save()
+ end
+end
+
+function DisplayOpenDocumentWindow()
+ OpenDocumentWindow:Initialise(function(self, success, path)
+ self:Close()
+ if success then
+ OpenDocument(path)
+ end
+ end):Show()
+end
+
+function OpenDocument(path)
+ Current.Selection = nil
+ local h = fs.open(path, 'r')
+ if h then
+ Current.Document = Document:Initialise(h.readAll(), RemoveExtension(fs.getName(path)), path)
+ else
+ ButtonDialougeWindow:Initialise('Error', 'An error occured while opening the file, try again.', 'Ok', nil, function(window, ok)
+ window:Close()
+ if Current.Document then
+ Current.TextInput = Current.Document.TextInput
+ end
+ end):Show()
+ end
+ h.close()
+end
+
+local TidyPath = function(path)
+ path = '/'..path
+ local fs = fs
+ if OneOS then
+ fs = OneOS.FS
+ end
+ if fs.isDir(path) then
+ path = path .. '/'
+ end
+
+ path, n = path:gsub("//", "/")
+ while n > 0 do
+ path, n = path:gsub("//", "/")
+ end
+ return path
+end
+
+OpenDocumentWindow = {
+ X = 1,
+ Y = 1,
+ Width = 0,
+ Height = 0,
+ CursorPos = 1,
+ Visible = true,
+ Return = nil,
+ OpenButton = nil,
+ PathTextBox = nil,
+ CurrentDirectory = '/',
+ Scroll = 0,
+ MaxScroll = 0,
+ GoUpButton = nil,
+ SelectedFile = '',
+ Files = {},
+ Typed = false,
+
+ AbsolutePosition = function(self)
+ return {X = self.X, Y = self.Y}
+ end,
+
+ Draw = function(self)
+ if not self.Visible then
+ return
+ end
+ Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey)
+ Drawing.DrawBlankArea(self.X, self.Y, self.Width, 3, colours.lightGrey)
+ Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-6, colours.white)
+ Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey)
+ Drawing.DrawBlankArea(self.X, self.Y + self.Height - 5, self.Width, 5, colours.lightGrey)
+ self:DrawFiles()
+
+ if (fs.exists(self.PathTextBox.TextInput.Value)) or (self.SelectedFile and #self.SelectedFile > 0 and fs.exists(self.CurrentDirectory .. self.SelectedFile)) then
+ self.OpenButton.TextColour = colours.black
+ else
+ self.OpenButton.TextColour = colours.lightGrey
+ end
+
+ self.PathTextBox:Draw()
+ self.OpenButton:Draw()
+ self.CancelButton:Draw()
+ self.GoUpButton:Draw()
+ end,
+
+ DrawFiles = function(self)
+ for i, file in ipairs(self.Files) do
+ if i > self.Scroll and i - self.Scroll <= 11 then
+ if file == self.SelectedFile then
+ Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue)
+ elseif string.find(file, '%.txt') or string.find(file, '%.text') or string.find(file, '%.ink') or fs.isDir(self.CurrentDirectory .. file) then
+ Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white)
+ else
+ Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.grey, colours.white)
+ end
+ end
+ end
+ self.MaxScroll = #self.Files - 11
+ if self.MaxScroll < 0 then
+ self.MaxScroll = 0
+ end
+ end,
+
+ Initialise = function(self, returnFunc)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.Width = 32
+ new.Height = 17
+ new.Return = returnFunc
+ new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2)
+ new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2)
+ new.Title = 'Open Document'
+ new.Visible = true
+ new.CurrentDirectory = '/'
+ new.SelectedFile = nil
+ if OneOS and fs.exists('/Desktop/Documents/') then
+ new.CurrentDirectory = '/Desktop/Documents/'
+ end
+ new.OpenButton = Button:Initialise(new.Width - 6, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle)
+ if fs.exists(new.PathTextBox.TextInput.Value) and self.TextColour == colours.black and not fs.isDir(new.PathTextBox.TextInput.Value) then
+ returnFunc(new, true, TidyPath(new.PathTextBox.TextInput.Value))
+ elseif new.SelectedFile and self.TextColour == colours.black and fs.isDir(new.CurrentDirectory .. new.SelectedFile) then
+ new:GoToDirectory(new.CurrentDirectory .. new.SelectedFile)
+ elseif new.SelectedFile and self.TextColour == colours.black then
+ returnFunc(new, true, TidyPath(new.CurrentDirectory .. '/' .. new.SelectedFile))
+ end
+ end, 'Open', colours.black)
+ new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle)
+ returnFunc(new, false)
+ end, 'Cancel', colours.black)
+ new.GoUpButton = Button:Initialise(2, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle)
+ local folderName = fs.getName(new.CurrentDirectory)
+ local parentDirectory = new.CurrentDirectory:sub(1, #new.CurrentDirectory-#folderName-1)
+ new:GoToDirectory(parentDirectory)
+ end, 'Go Up', colours.black)
+ new.PathTextBox = TextBox:Initialise(2, new.Height - 3, new.Width - 2, 1, new, new.CurrentDirectory, colours.white, colours.black)
+ new:GoToDirectory(new.CurrentDirectory)
+ return new
+ end,
+
+ Show = function(self)
+ Current.Window = self
+ return self
+ end,
+
+ Close = function(self)
+ Current.Input = nil
+ Current.Window = nil
+ self = nil
+ end,
+
+ GoToDirectory = function(self, path)
+ path = TidyPath(path)
+ self.CurrentDirectory = path
+ self.Scroll = 0
+ self.SelectedFile = nil
+ self.Typed = false
+ self.PathTextBox.TextInput.Value = path
+ local fs = fs
+ if OneOS then
+ fs = OneOS.FS
+ end
+ self.Files = fs.list(self.CurrentDirectory)
+ Draw()
+ end,
+
+ Flash = function(self)
+ self.Visible = false
+ Draw()
+ sleep(0.15)
+ self.Visible = true
+ Draw()
+ sleep(0.15)
+ self.Visible = false
+ Draw()
+ sleep(0.15)
+ self.Visible = true
+ Draw()
+ end,
+
+ Click = function(self, side, x, y)
+ local items = {self.OpenButton, self.CancelButton, self.PathTextBox, self.GoUpButton}
+ local found = false
+ for i, v in ipairs(items) do
+ if CheckClick(v, x, y) then
+ v:Click(side, x, y)
+ found = true
+ end
+ end
+
+ if not found then
+ if y <= 12 then
+ local fs = fs
+ if OneOS then
+ fs = OneOS.FS
+ end
+ self.SelectedFile = fs.list(self.CurrentDirectory)[y-1]
+ self.PathTextBox.TextInput.Value = TidyPath(self.CurrentDirectory .. '/' .. self.SelectedFile)
+ Draw()
+ end
+ end
+ return true
+ end
+}
+
+PrintDocumentWindow = {
+ X = 1,
+ Y = 1,
+ Width = 0,
+ Height = 0,
+ CursorPos = 1,
+ Visible = true,
+ Return = nil,
+ PrintButton = nil,
+ CopiesTextBox = nil,
+ Scroll = 0,
+ MaxScroll = 0,
+ PrinterSelectButton = nil,
+ Title = '',
+ Status = 0, --0 = neutral, 1 = good, -1 = error
+ StatusText = '',
+ SelectedPrinter = nil,
+
+ AbsolutePosition = function(self)
+ return {X = self.X, Y = self.Y}
+ end,
+
+ Draw = function(self)
+ if not self.Visible then
+ return
+ end
+ Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey)
+ Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey)
+ Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white)
+ Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey)
+
+ self.PrinterSelectButton:Draw()
+ Drawing.DrawCharactersCenter(self.X, self.Y + self.PrinterSelectButton.Y - 2, self.Width, 1, 'Printer', colours.black, colours.white)
+ Drawing.DrawCharacters(self.X + self.Width - 3, self.Y + self.PrinterSelectButton.Y - 1, '\\/', colours.black, colours.lightGrey)
+ Drawing.DrawCharacters(self.X + 1, self.Y + self.CopiesTextBox.Y - 1, 'Copies', colours.black, colours.white)
+ local statusColour = colours.grey
+ if self.Status == -1 then
+ statusColour = colours.red
+ elseif self.Status == 1 then
+ statusColour = colours.green
+ end
+ Drawing.DrawCharacters(self.X + 1, self.Y + self.CopiesTextBox.Y + 1, self.StatusText, statusColour, colours.white)
+
+ self.CopiesTextBox:Draw()
+ self.PrintButton:Draw()
+ self.CancelButton:Draw()
+ end,
+
+ Initialise = function(self)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.Width = 32
+ new.Height = 11
+ new.Return = returnFunc
+ new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2)
+ new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2)
+ new.Title = 'Print Document'
+ new.Visible = true
+ new.PrintButton = Button:Initialise(new.Width - 7, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle)
+ local doPrint = true
+ if new.SelectedPrinter == nil then
+ local p = Peripheral.GetPeripheral('printer')
+ if p then
+ new.SelectedPrinter = p.Side
+ new.PrinterSelectButton.Text = p.Fullname
+ else
+ new.StatusText = 'No Connected Printer'
+ new.Status = -1
+ doPrint = false
+ end
+ end
+ if doPrint then
+ local printer = Printer:Initialise(new.SelectedPrinter)
+ local err = printer:PrintLines(Current.Document.Lines, Current.Document.Title, tonumber(new.CopiesTextBox.TextInput.Value))
+ if not err then
+ new.StatusText = 'Document Printed!'
+ new.Status = 1
+ closeWindowTimer = os.startTimer(1)
+ else
+ new.StatusText = err
+ new.Status = -1
+ end
+ end
+ end, 'Print', colours.black)
+ new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle)
+ new:Close()
+ Draw()
+ end, 'Close', colours.black)
+ new.PrinterSelectButton = Button:Initialise(2, 4, new.Width - 2, nil, colours.lightGrey, new, function(self, side, x, y, toggle)
+ local printers = {
+ {
+ Title = "Automatic",
+ Click = function()
+ new.SelectedPrinter = nil
+ new.PrinterSelectButton.Text = 'Automatic'
+ end
+ },
+ {
+ Separator = true
+ }
+ }
+ for i, p in ipairs(Peripheral.GetPeripherals('printer')) do
+ table.insert(printers, {
+ Title = p.Fullname,
+ Click = function(self)
+ new.SelectedPrinter = p.Side
+ new.PrinterSelectButton.Text = p.Fullname
+ end
+ })
+ end
+ Current.Menu = Menu:New(x, y+4, printers, self, true)
+ end, 'Automatic', colours.black)
+ new.CopiesTextBox = TextBox:Initialise(9, 6, 4, 1, new, 1, colours.lightGrey, colours.black, nil, true)
+ Current.TextInput = new.CopiesTextBox.TextInput
+ new.StatusText = 'Waiting...'
+ new.Status = 0
+ return new
+ end,
+
+ Show = function(self)
+ Current.Window = self
+ return self
+ end,
+
+ Close = function(self)
+ Current.Input = nil
+ Current.Window = nil
+ self = nil
+ end,
+
+ Flash = function(self)
+ self.Visible = false
+ Draw()
+ sleep(0.15)
+ self.Visible = true
+ Draw()
+ sleep(0.15)
+ self.Visible = false
+ Draw()
+ sleep(0.15)
+ self.Visible = true
+ Draw()
+ end,
+
+ Click = function(self, side, x, y)
+ local items = {self.PrintButton, self.CancelButton, self.CopiesTextBox, self.PrinterSelectButton}
+ for i, v in ipairs(items) do
+ if CheckClick(v, x, y) then
+ v:Click(side, x, y)
+ end
+ end
+ return true
+ end
+}
+
+SaveDocumentWindow = {
+ X = 1,
+ Y = 1,
+ Width = 0,
+ Height = 0,
+ CursorPos = 1,
+ Visible = true,
+ Return = nil,
+ SaveButton = nil,
+ PathTextBox = nil,
+ CurrentDirectory = '/',
+ Scroll = 0,
+ MaxScroll = 0,
+ ScrollBar = nil,
+ GoUpButton = nil,
+ Files = {},
+ Typed = false,
+
+ AbsolutePosition = function(self)
+ return {X = self.X, Y = self.Y}
+ end,
+
+ Draw = function(self)
+ if not self.Visible then
+ return
+ end
+ Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey)
+ Drawing.DrawBlankArea(self.X, self.Y, self.Width, 3, colours.lightGrey)
+ Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-6, colours.white)
+ Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey)
+ Drawing.DrawBlankArea(self.X, self.Y + self.Height - 5, self.Width, 5, colours.lightGrey)
+ Drawing.DrawCharacters(self.X + 1, self.Y + self.Height - 5, self.CurrentDirectory, colours.grey, colours.lightGrey)
+ self:DrawFiles()
+
+ if (self.PathTextBox.TextInput.Value) then
+ self.SaveButton.TextColour = colours.black
+ else
+ self.SaveButton.TextColour = colours.lightGrey
+ end
+
+ self.PathTextBox:Draw()
+ self.SaveButton:Draw()
+ self.CancelButton:Draw()
+ self.GoUpButton:Draw()
+ end,
+
+ DrawFiles = function(self)
+ for i, file in ipairs(self.Files) do
+ if i > self.Scroll and i - self.Scroll <= 10 then
+ if file == self.SelectedFile then
+ Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue)
+ elseif fs.isDir(self.CurrentDirectory .. file) then
+ Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white)
+ else
+ Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.lightGrey, colours.white)
+ end
+ end
+ end
+ self.MaxScroll = #self.Files - 11
+ if self.MaxScroll < 0 then
+ self.MaxScroll = 0
+ end
+ end,
+
+ Initialise = function(self, returnFunc)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.Width = 32
+ new.Height = 16
+ new.Return = returnFunc
+ new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2)
+ new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2)
+ new.Title = 'Save Document'
+ new.Visible = true
+ new.CurrentDirectory = '/'
+ if OneOS and fs.exists('/Desktop/Documents/') then
+ new.CurrentDirectory = '/Desktop/Documents/'
+ end
+ new.SaveButton = Button:Initialise(new.Width - 6, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle)
+ if self.TextColour == colours.black and not fs.isDir(new.CurrentDirectory ..'/' .. new.PathTextBox.TextInput.Value) then
+ returnFunc(new, true, TidyPath(new.CurrentDirectory ..'/' .. new.PathTextBox.TextInput.Value))
+ elseif new.SelectedFile and self.TextColour == colours.black and fs.isDir(new.CurrentDirectory .. new.SelectedFile) then
+ new:GoToDirectory(new.CurrentDirectory .. new.SelectedFile)
+ end
+ end, 'Save', colours.black)
+ new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle)
+ returnFunc(new, false)
+ end, 'Cancel', colours.black)
+ new.GoUpButton = Button:Initialise(2, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle)
+ local folderName = fs.getName(new.CurrentDirectory)
+ local parentDirectory = new.CurrentDirectory:sub(1, #new.CurrentDirectory-#folderName-1)
+ new:GoToDirectory(parentDirectory)
+ end, 'Go Up', colours.black)
+ new.PathTextBox = TextBox:Initialise(2, new.Height - 3, new.Width - 2, 1, new, '', colours.white, colours.black, function(key)
+ if key == keys.enter then
+ new.SaveButton:Click()
+ end
+ end)
+ new.PathTextBox.Placeholder = 'Document Name'
+ Current.TextInput = new.PathTextBox.TextInput
+ new:GoToDirectory(new.CurrentDirectory)
+ return new
+ end,
+
+ Show = function(self)
+ Current.Window = self
+ return self
+ end,
+
+ Close = function(self)
+ Current.Input = nil
+ Current.Window = nil
+ self = nil
+ end,
+
+ GoToDirectory = function(self, path)
+ path = TidyPath(path)
+ self.CurrentDirectory = path
+ self.Scroll = 0
+ self.Typed = false
+ local fs = fs
+ if OneOS then
+ fs = OneOS.FS
+ end
+ self.Files = fs.list(self.CurrentDirectory)
+ Draw()
+ end,
+
+ Flash = function(self)
+ self.Visible = false
+ Draw()
+ sleep(0.15)
+ self.Visible = true
+ Draw()
+ sleep(0.15)
+ self.Visible = false
+ Draw()
+ sleep(0.15)
+ self.Visible = true
+ Draw()
+ end,
+
+ Click = function(self, side, x, y)
+ local items = {self.SaveButton, self.CancelButton, self.PathTextBox, self.GoUpButton}
+ local found = false
+ for i, v in ipairs(items) do
+ if CheckClick(v, x, y) then
+ v:Click(side, x, y)
+ found = true
+ end
+ end
+
+ if not found then
+ if y <= 11 then
+ local files = fs.list(self.CurrentDirectory)
+ if files[y-1] then
+ self:GoToDirectory(self.CurrentDirectory..files[y-1])
+ Draw()
+ end
+ end
+ end
+ return true
+ end
+}
+
+local WrapText = function(text, maxWidth)
+ local lines = {''}
+ for word, space in text:gmatch('(%S+)(%s*)') do
+ local temp = lines[#lines] .. word .. space:gsub('\n','')
+ if #temp > maxWidth then
+ table.insert(lines, '')
+ end
+ if space:find('\n') then
+ lines[#lines] = lines[#lines] .. word
+
+ space = space:gsub('\n', function()
+ table.insert(lines, '')
+ return ''
+ end)
+ else
+ lines[#lines] = lines[#lines] .. word .. space
+ end
+ end
+ return lines
+end
+
+ButtonDialougeWindow = {
+ X = 1,
+ Y = 1,
+ Width = 0,
+ Height = 0,
+ CursorPos = 1,
+ Visible = true,
+ CancelButton = nil,
+ OkButton = nil,
+ Lines = {},
+
+ AbsolutePosition = function(self)
+ return {X = self.X, Y = self.Y}
+ end,
+
+ Draw = function(self)
+ if not self.Visible then
+ return
+ end
+ Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey)
+ Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey)
+ Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white)
+ Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey)
+
+ for i, text in ipairs(self.Lines) do
+ Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white)
+ end
+
+ self.OkButton:Draw()
+ if self.CancelButton then
+ self.CancelButton:Draw()
+ end
+ end,
+
+ Initialise = function(self, title, message, okText, cancelText, returnFunc)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.Width = 28
+ new.Lines = WrapText(message, new.Width - 2)
+ new.Height = 5 + #new.Lines
+ new.Return = returnFunc
+ new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2)
+ new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2)
+ new.Title = title
+ new.Visible = true
+ new.Visible = true
+ new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function()
+ returnFunc(new, true)
+ end, okText)
+ if cancelText then
+ new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function()
+ returnFunc(new, false)
+ end, cancelText)
+ end
+
+ return new
+ end,
+
+ Show = function(self)
+ Current.Window = self
+ return self
+ end,
+
+ Close = function(self)
+ Current.Window = nil
+ self = nil
+ end,
+
+ Flash = function(self)
+ self.Visible = false
+ Draw()
+ sleep(0.15)
+ self.Visible = true
+ Draw()
+ sleep(0.15)
+ self.Visible = false
+ Draw()
+ sleep(0.15)
+ self.Visible = true
+ Draw()
+ end,
+
+ Click = function(self, side, x, y)
+ local items = {self.OkButton, self.CancelButton}
+ local found = false
+ for i, v in ipairs(items) do
+ if CheckClick(v, x, y) then
+ v:Click(side, x, y)
+ found = true
+ end
+ end
+ return true
+ end
+}
+
+TextDialougeWindow = {
+ X = 1,
+ Y = 1,
+ Width = 0,
+ Height = 0,
+ CursorPos = 1,
+ Visible = true,
+ CancelButton = nil,
+ OkButton = nil,
+ Lines = {},
+ TextInput = nil,
+
+ AbsolutePosition = function(self)
+ return {X = self.X, Y = self.Y}
+ end,
+
+ Draw = function(self)
+ if not self.Visible then
+ return
+ end
+ Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey)
+ Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey)
+ Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white)
+ Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey)
+
+ for i, text in ipairs(self.Lines) do
+ Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white)
+ end
+
+
+ Drawing.DrawBlankArea(self.X + 1, self.Y + self.Height - 4, self.Width - 2, 1, colours.lightGrey)
+ Drawing.DrawCharacters(self.X + 2, self.Y + self.Height - 4, self.TextInput.Value, colours.black, colours.lightGrey)
+ Current.CursorPos = {self.X + 2 + self.TextInput.CursorPos, self.Y + self.Height - 4}
+ Current.CursorColour = colours.black
+
+ self.OkButton:Draw()
+ if self.CancelButton then
+ self.CancelButton:Draw()
+ end
+ end,
+
+ Initialise = function(self, title, message, okText, cancelText, returnFunc, numerical)
+ local new = {} -- the new instance
+ setmetatable( new, {__index = self} )
+ new.Width = 28
+ new.Lines = WrapText(message, new.Width - 2)
+ new.Height = 7 + #new.Lines
+ new.Return = returnFunc
+ new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2)
+ new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2)
+ new.Title = title
+ new.Visible = true
+ new.Visible = true
+ new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function()
+ if #new.TextInput.Value > 0 then
+ returnFunc(new, true, new.TextInput.Value)
+ end
+ end, okText)
+ if cancelText then
+ new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function()
+ returnFunc(new, false)
+ end, cancelText)
+ end
+ new.TextInput = TextInput:Initialise('', function(enter)
+ if enter then
+ new.OkButton:Click()
+ end
+ Draw()
+ end, numerical)
+
+ Current.Input = new.TextInput
+
+ return new
+ end,
+
+ Show = function(self)
+ Current.Window = self
+ return self
+ end,
+
+ Close = function(self)
+ Current.Window = nil
+ Current.Input = nil
+ self = nil
+ end,
+
+ Flash = function(self)
+ self.Visible = false
+ Draw()
+ sleep(0.15)
+ self.Visible = true
+ Draw()
+ sleep(0.15)
+ self.Visible = false
+ Draw()
+ sleep(0.15)
+ self.Visible = true
+ Draw()
+ end,
+
+ Click = function(self, side, x, y)
+ local items = {self.OkButton, self.CancelButton}
+ local found = false
+ for i, v in ipairs(items) do
+ if CheckClick(v, x, y) then
+ v:Click(side, x, y)
+ found = true
+ end
+ end
+ return true
+ end
+}
+
+function PrintCentered(text, y)
+ local w, h = term.getSize()
+ x = math.ceil(math.ceil((w / 2) - (#text / 2)), 0)+1
+ term.setCursorPos(x, y)
+ print(text)
+end
+
+function DoVanillaClose()
+ term.setBackgroundColour(colours.black)
+ term.setTextColour(colours.white)
+ term.clear()
+ term.setCursorPos(1, 1)
+ PrintCentered("Thanks for using Ink!", (Drawing.Screen.Height/2)-1)
+ term.setTextColour(colours.lightGrey)
+ PrintCentered("Word Proccessor for ComputerCraft", (Drawing.Screen.Height/2))
+ term.setTextColour(colours.white)
+ PrintCentered("(c) oeed 2014", (Drawing.Screen.Height/2)+3)
+ term.setCursorPos(1, Drawing.Screen.Height)
+ error('', 0)
+end
+
+function Close()
+ if isQuitting or not Current.Document or not Current.Modified then
+ if not OneOS then
+ DoVanillaClose()
+ end
+ return true
+ else
+ local _w = ButtonDialougeWindow:Initialise('Quit Ink?', 'You have unsaved changes, do you want to quit anyway?', 'Quit', 'Cancel', function(window, success)
+ if success then
+ if OneOS then
+ OneOS.Close(true)
+ else
+ DoVanillaClose()
+ end
+ end
+ window:Close()
+ Draw()
+ end):Show()
+ --it's hacky but it works
+ os.queueEvent('mouse_click', 1, _w.X, _w.Y)
+ return false
+ end
+end
+
+if OneOS then
+ OneOS.CanClose = function()
+ return Close()
+ end
+end
+
+Initialise() \ No newline at end of file
diff --git a/programs/ink.shortcut b/programs/ink.shortcut
new file mode 100644
index 0000000..a8b8d12
--- /dev/null
+++ b/programs/ink.shortcut
@@ -0,0 +1,2 @@
+name: Ink
+shortcut: /programs/ink.lua \ No newline at end of file
diff --git a/programs/luaide.shortcut b/programs/luaide.shortcut
new file mode 100644
index 0000000..fe073a7
--- /dev/null
+++ b/programs/luaide.shortcut
@@ -0,0 +1,2 @@
+name: LuaIDE
+shortcut: /programs/luaide.lua \ No newline at end of file
diff --git a/programs/mousebrowser.lua b/programs/mousebrowser.lua
new file mode 100644
index 0000000..6757273
--- /dev/null
+++ b/programs/mousebrowser.lua
@@ -0,0 +1,1298 @@
+--[[
+ Mouse File Browser
+ by:
+ Stiepen irc(Kilobyte)
+ Cruor
+ BigSHinyToys
+
+ note: send link to nightin9ale on CC forums
+--]]
+
+local tArgs = {...}
+local ver = "1.4"
+local sTitle = "File Browser"
+local bugTest, norun, dir, showAll
+local _tArgs = {}
+local config = "mouse.cfg"
+
+local temp
+if shell and shell.getRunningProgram then
+ temp = shell.getRunningProgram()
+end
+
+temp = temp or "/bla"
+local localPath = string.sub(temp,1,#temp-string.len(fs.getName(temp)))
+temp = nil -- just because not needed
+
+-- load config file
+
+local configSet = {}
+local cnf = {}
+
+if fs.exists(localPath.."/"..config) then
+ local file = fs.open(localPath.."/"..config,"r")
+ if file then
+ local item = file.readLine()
+ while item do
+ table.insert(cnf,item)
+ item = file.readLine()
+ end
+ file.close()
+ end
+end
+
+for i = 1,10 do
+ local test,data = pcall(textutils.unserialize,cnf[i])
+ if test then
+ configSet[i] = data
+ else
+ configSet[i] = nil
+ end
+end
+cnf = nil
+
+-- color configuration work in progress
+local titleBar = configSet[1] or {txt = colors.black,back = colors.blue}
+local addressBar = configSet[2] or {txt = colors.black,back = colors.lightGray}
+local itemWindo = configSet[3] or {txt = colors.black,back = colors.cyan}
+local rcmList = configSet[4] or {txt = colors.black,back = colors.lightGray} -- rcm = Right Click Menu List
+local rcmTitle = configSet[5] or {txt = colors.black,back = colors.blue}
+local dialogTitle = configSet[6] or {txt = colors.black,back = colors.blue}
+local dialogWindo = configSet[7] or {txt = colors.black,back = colors.white}
+local scrollCol = configSet[8] or {off = colors.gray, button = colors.gray,back = colors.lightGray}
+
+local tIcons = configSet[9] or {
+ back = {tCol = "lightGray",bCol = "blue",txt = " < "},
+ disk = {tCol = "lime",bCol = "green",txt = "[*]"},
+ audio = {tCol = "yellow",bCol = "red",txt = "(o)"},
+ folder = {tCol = "lightGray",bCol = "blue",txt = "[=]"},
+ file = {tCol = nil ,bCol = nil ,txt = nil}
+}
+
+local customLaunch = configSet[10] or {
+ ["Edit"] = "rom/programs/edit",
+ ["Paint"] = "rom/programs/color/paint"
+}
+
+local function saveCFG(overWrite)
+ if not fs.exists(localPath.."/"..config) or overWrite then
+ local cnf = {}
+ local file = fs.open(localPath.."/"..config,"w")
+ if file then
+ file.write(textutils.serialize(titleBar).."\n")
+ file.write(textutils.serialize(addressBar).."\n")
+ file.write(textutils.serialize(itemWindo).."\n")
+ file.write(textutils.serialize(rcmList).."\n")
+ file.write(textutils.serialize(rcmTitle).."\n")
+ file.write(textutils.serialize(dialogTitle).."\n")
+ file.write(textutils.serialize(dialogWindo).."\n")
+ file.write(textutils.serialize(scrollCol).."\n")
+ file.write(textutils.serialize(tIcons).."\n")
+ file.write(textutils.serialize(customLaunch).."\n")
+ file.close()
+ elseif overWrite then
+ end
+ end
+end
+
+saveCFG()
+
+-- end configuration
+
+local function help()
+ print([[Usage: browser [-d] [-h] [-a] [-u] [--debug] [--help] [--dir <dir>] [--all] [--update]
+--debug or -d: enable debug mode
+--help or -h: display this screen
+--dir: define initial directory
+--all or -a: show hidden files
+--update -u: update]])
+end
+
+local function inBouwndry(clickX,clickY,boxX,boxY,width,hight)
+ return ( clickX >= boxX and clickX < boxX + width and clickY >= boxY and clickY < boxY + hight )
+end
+
+local function update()
+ print("Checking for Updates")
+ local isHTTP = false
+ local response
+ if http then
+ isHTTP = true
+ print("http on")
+ response = http.get("http://pastebin.com/raw.php?i=rLbnyM1U")
+ end
+ local flag = false
+ local isNewFlag = false
+ local newVerID
+ if response and isHTTP then
+ print("recived")
+ local sInfo = response.readLine()
+ print(sInfo)
+ while sInfo do
+ print(sInfo)
+ if flag then
+ if sInfo == ver then
+ print("Mouse File Browser is up to date")
+ break
+ else
+ newVerID = sInfo
+ flag = false
+ isNewFlag = true
+ end
+ elseif sInfo == sTitle then
+ flag = true
+ elseif isNewFlag then
+ isNewFlag = sInfo
+ response.close()
+ break
+ end
+ sInfo = response.readLine()
+ end
+ if isNewFlag then
+ print("New vershion avalible "..newVerID)
+ print('downloading to \Browser')
+ if fs.exists("Browser") then
+ write("Browser exists OverWrite Browser Y/N : ")
+ local input = string.lower(read())
+ while input ~= "y" and input ~= "n" do
+ print("y or n required")
+ input = string.lower(read())
+ end
+ if input == "y" then
+ print("Over Writeing Browser")
+ print("Downloading new File")
+ local response = http.get("http://pastebin.com/raw.php?i="..isNewFlag)
+ if response then
+ print("file downloaded")
+ print("installing")
+ fs.delete("Browser")
+ local handel = fs.open("Browser","w")
+ if handel then
+ handel.write(response.readAll())
+ handel.close()
+ print("Update Complete")
+ end
+ response.close()
+ end
+ else
+ print("Update aborted")
+ end
+ else
+ print("Downloading new File")
+ local response = http.get("http://pastebin.com/raw.php?i="..isNewFlag)
+ if response then
+ print("file downloaded")
+ print("installing")
+ local handel = fs.open("Browser","w")
+ if handel then
+ handel.write(response.readAll())
+ handel.close()
+ print("Update Complete")
+ end
+ response.close()
+ end
+ end
+ end
+ elseif isHTTP then
+ print("Error downloading update file Please contact BigSHinyToys on the CC forums")
+ print("http://www.computercraft.info/forums2/index.php?/topic/5509-advanced-computer-mouse-file-browser/")
+ elseif not isHTTP then
+ print("HTTP API is turned off")
+ print("Access Computer Craft Configer and change line")
+ print([[enableapi_http {
+# Enable the "http" API on Computers
+general=false
+}
+TO THIS :
+enableapi_http {
+# Enable the "http" API on Computers
+general=true
+}]])
+ end
+ notCMD = false
+ norun = true
+end
+
+for a = 1, #tArgs do
+ if tArgs[a]:sub(1,2) == "--" then
+ local cmd = tArgs[a]:sub(3):lower()
+ if cmd == "debug" then
+ bugTest = true
+ elseif cmd == "help" then
+ help()
+ norun = true
+ elseif cmd == "dir" then
+ dir = tArgs[a+1]
+ a = a + 1
+ elseif cmd == "all" then
+ showAll = true
+ elseif cmd == "update" then
+ update()
+ end
+ elseif tArgs[a]:sub(1,1) == "-" then
+ for b = 2, #tArgs[a] do
+ cmd = tArgs[a]:sub(b, b)
+ if cmd == "d" then
+ bugTest = true
+ elseif cmd == "h" then
+ help()
+ norun = true
+ elseif cmd == "p" then
+ dir = tArgs[a+1]
+ a = a + 1
+ elseif cmd == "a" then
+ showAll = true
+ elseif cmd == "u" then
+ update()
+ end
+ end
+ else
+ table.insert(_tArgs, tArgs[a])
+ end
+end
+
+if (not dir) and shell and shell.dir then
+ dir = shell.dir()
+end
+
+if dir and shell and shell.resolve then
+ dir = shell.resolve(dir)
+end
+
+dir = dir or "/"
+
+if bugTest then -- this is that the var is for testing
+ print("Dir: "..dir)
+ os.startTimer(4)
+ os.pullEvent()
+end
+
+local function clear()
+ term.setBackgroundColor(colors.black)
+ term.setTextColor(colors.white)
+ term.clear()
+ term.setCursorBlink(false)
+ term.setCursorPos(1,1)
+end
+
+--[[
+ Code thanks to Cruor
+ http://www.computercraft.info/forums2/index.php?/topic/5802-support-for-shell/
+]]--
+
+local function fixArgs(...)
+ local tReturn={}
+ local str=table.concat({...}," ")
+ local sMatch
+ while str and #str>0 do
+ if string.sub(str,1,1)=="\"" then
+ sMatch, str=string.match(str, "\"(.-)\"%s*(.*)")
+ else
+ sMatch, str=string.match(str, "(%S+)%s*(.*)")
+ end
+ table.insert(tReturn,sMatch)
+ end
+ return tReturn
+end
+
+--[[ end Cruor function ]]--
+
+
+-- modified read made to play nice with coroutines
+
+local function readMOD( _sReplaceChar, _tHistory,_wdth)
+ local sLine = ""
+ term.setCursorBlink( true )
+
+ local nHistoryPos = nil
+ local nPos = 0
+ if _sReplaceChar then
+ _sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
+ end
+
+ local sx, sy = term.getCursorPos()
+
+ local w, h = term.getSize()
+ if _wdth and type(_wdth) == "number" then
+ w = sx + _wdth - 1
+ end
+
+ local function redraw( _sCustomReplaceChar )
+ local nScroll = 0
+ if sx + nPos >= w then
+ nScroll = (sx + nPos) - w
+ end
+
+ term.setCursorPos( sx + _wdth - 1, sy )
+ term.write(" ")
+ term.setCursorPos( sx, sy )
+ local sReplace = _sCustomReplaceChar or _sReplaceChar
+ if sReplace then
+ term.write( string.rep(sReplace,_wdth) )
+ else
+ term.write( string.sub( sLine, nScroll + 1 ,nScroll + _wdth) )
+ end
+ term.setCursorPos( sx + nPos - nScroll, sy )
+ end
+
+ while true do
+ local sEvent, param = os.pullEvent()
+ if sEvent == "char" then
+ sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
+ nPos = nPos + 1
+ redraw()
+
+ elseif sEvent == "key" then
+
+ if param == keys.left then
+ -- Left
+ if nPos > 0 then
+ nPos = nPos - 1
+ redraw()
+ end
+
+ elseif param == keys.right then
+ -- Right
+ if nPos < string.len(sLine) then
+ nPos = nPos + 1
+ redraw()
+ end
+
+ elseif param == keys.up or param == keys.down then
+ -- Up or down
+ if _tHistory then
+ redraw(" ");
+ if param == keys.up then
+ -- Up
+ if nHistoryPos == nil then
+ if #_tHistory > 0 then
+ nHistoryPos = #_tHistory
+ end
+ elseif nHistoryPos > 1 then
+ nHistoryPos = nHistoryPos - 1
+ end
+ else
+ -- Down
+ if nHistoryPos == #_tHistory then
+ nHistoryPos = nil
+ elseif nHistoryPos ~= nil then
+ nHistoryPos = nHistoryPos + 1
+ end
+ end
+
+ if nHistoryPos then
+ sLine = _tHistory[nHistoryPos]
+ nPos = string.len( sLine )
+ else
+ sLine = ""
+ nPos = 0
+ end
+ redraw()
+ end
+ elseif param == keys.backspace then
+ -- Backspace
+ if nPos > 0 then
+ redraw(" ");
+ sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
+ nPos = nPos - 1
+ redraw()
+ end
+ elseif param == keys.home then
+ -- Home
+ nPos = 0
+ redraw()
+ elseif param == keys.delete then
+ if nPos < string.len(sLine) then
+ redraw(" ");
+ sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )
+ redraw()
+ end
+ elseif param == keys["end"] then
+ -- End
+ nPos = string.len(sLine)
+ redraw()
+ end
+ elseif sEvent == "redraw" then
+ redraw()
+ elseif sEvent == "return" then
+ term.setCursorBlink( false )
+ return sLine
+ end
+ end
+
+ term.setCursorBlink( false )
+
+ return sLine
+end
+
+-- end modified read
+
+local function printC(posX,posY,textCol,backCol,text)
+ term.setCursorPos(posX,posY)
+ term.setTextColor(colors[textCol] or textCol)
+ term.setBackgroundColor(colors[backCol] or backCol)
+ term.write(text)
+end
+
+local function InputBox(title)
+ local boxW,boxH = 26,3
+ local termX,termY = term.getSize()
+ local ofsX,ofsY = math.ceil((termX/2) - (boxW/2)) , math.ceil((termY/2) - (boxH/2)) -- offset from top left
+ local options = {"ok","cancel"}
+
+ local selected = 1
+ local space = 0
+ local range = {}
+ for i = 1,#options do
+ range[i] = {s = space,f = space + string.len(options[i])}
+ space = space + string.len(options[i])+3
+ end
+ local ofC = (boxW/2) - (space/2)
+
+ local function drawBox()
+ printC(ofsX,ofsY,colors.black,colors.blue,string.rep(" ",boxW))
+ printC(ofsX+1,ofsY,colors.black,colors.blue,(title or "User Input"))
+ printC(ofsX,ofsY+1,colors.black,colors.white,string.rep(" ",boxW))
+ printC(ofsX,ofsY+2,colors.black,colors.white,string.rep(" ",boxW))
+ printC(ofsX,ofsY+3,colors.black,colors.white,string.rep(" ",boxW))
+
+ for i = 1,#options do
+ if i == selected then
+ term.setBackgroundColor(colors.lightGray)
+ term.setCursorPos(range[i].s + ofC + ofsX - 1,ofsY + 3)
+ term.write("["..options[i].."]")
+ term.setBackgroundColor(colors.white)
+ term.write(" ")
+ else
+ term.setCursorPos(range[i].s + ofC + ofsX - 1,ofsY + 3)
+ term.write(" "..options[i].." ")
+ end
+ end
+
+ printC(ofsX+2,ofsY+2,colors.black,colors.lightGray,string.rep(" ",boxW-4))
+ end
+ drawBox()
+ term.setCursorPos(ofsX+2,ofsY+2)
+ local co = coroutine.create(function() return readMOD(nil,nil,boxW - 4) end)
+ while true do
+ local event = {os.pullEvent()}
+ if event[1] == "key" or event[1] == "char" then
+ if event[2] == 28 then
+ local test,data = coroutine.resume(co,"return")
+ return data
+ else
+ coroutine.resume(co,unpack(event))
+ end
+ elseif event[1] == "mouse_click" then
+ if event[4] == ofsY + 3 then
+ for i = 1,#options do
+ if event[3] >= range[i].s + ofC + ofsX - 1 and event[3] <= range[i].f + ofC + ofsX then
+ if options[i] == "ok" then
+ local test,data = coroutine.resume(co,"return")
+ return data
+ elseif options[i] == "cancel" then
+ return false
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+local function dialogBox(title,message,options, h, w)
+ term.setCursorBlink(false)
+ local selected = 1
+ title = title or ""
+ message = message or ""
+ options = options or {}
+ local boxW,boxH = (w or 26), (h or 3)
+ local termX,termY = term.getSize()
+ local ofsX,ofsY = math.ceil((termX/2) - (boxW/2)) , math.ceil((termY/2) - (boxH/2)) -- offset from top left
+
+ local space = 0
+ local range = {}
+ for i = 1,#options do
+ range[i] = {s = space,f = space + string.len(options[i])}
+ space = space + string.len(options[i])+3
+ end
+ local ofC = math.ceil((boxW/2)) - math.ceil((space/2))
+
+ local function drawBox()
+ printC(ofsX,ofsY,dialogTitle.txt,dialogTitle.back," "..title..string.rep(" ",boxW-#title-5).."_[]")
+ term.setBackgroundColor(colors.red)
+ term.setTextColor(colors.white)
+ term.write("X")
+ printC(ofsX,ofsY+1,dialogWindo.txt,dialogWindo.back,string.sub(" "..message..string.rep(" ",boxW),1,boxW))
+ term.setCursorPos(ofsX,ofsY+2)
+ term.write(string.rep(" ",boxW))
+ term.setCursorPos(ofsX,ofsY+3)
+ term.write(string.rep(" ",boxW))
+ for i = 1,#options do
+ if i == selected then
+ printC(range[i].s + ofC + ofsX - 1,ofsY + 3,"black","lightGray","["..options[i].."]")
+ term.setBackgroundColor(dialogWindo.back)
+ term.setTextColor(dialogWindo.txt)
+ term.write(" ")
+ else
+ term.setCursorPos(range[i].s + ofC + ofsX - 1,ofsY + 3)
+ term.write(" "..options[i].." ")
+ end
+ end
+ term.setCursorPos(ofsX + ofC + space,ofsY + 3)
+ term.write(string.rep(" ",boxW - (ofC + space)))
+ term.setBackgroundColor(colors.black)
+ term.setTextColor(colors.white)
+ end
+ while true do
+ drawBox()
+ event = {os.pullEvent()}
+ if event[1] == "key" then
+ if event[2] == 203 then -- left
+ selected = selected - 1
+ if selected < 1 then
+ selected = #options
+ end
+ elseif event[2] == 205 then -- right
+ selected = selected + 1
+ if selected > #options then
+ selected = 1
+ end
+ elseif event[2] == 28 then -- enter
+ return selected , options[selected]
+ end
+ elseif event[1] == "mouse_click" then
+
+ if bugTest then term.write("M "..event[2].." X "..event[3].." Y "..event[4].." ") end
+
+ if event[2] == 1 then
+ if event[4] == ofsY + 3 then
+ for i = 1,#options do
+ if event[3] >= range[i].s + ofC + ofsX - 1 and event[3] <= range[i].f + ofC + ofsX then
+ return i , options[i]
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+local flag = true
+local fSlash = "/"
+local path = {dir:match("[^/]+")}
+local function stringPath() -- compacted this a lot
+ return fSlash..table.concat(path,fSlash)
+end
+
+local function osRunSpaces(sFileLocation,...) -- getRunningProgram() ["shell"] = shell
+ clear()
+ if os.newThread then
+ os.newThread(false,...)
+ else
+ local fProg,probblem = loadfile(sFileLocation)
+ if fProg then
+ local tEnv = {["shell"] = {}}
+ setmetatable(tEnv.shell,{ __index = shell})
+ tEnv.shell.getRunningProgram = function()
+ return sFileLocation
+ end
+ setmetatable(tEnv,{ __index = _G})
+ setfenv(fProg,tEnv)
+ local test,probblem = pcall(fProg,...)
+ if not test then
+ print(probblem)
+ dialogBox("ERROR",tostring(probblem),{"ok"},3,30)
+ else
+ return true
+ end
+ else
+ print(probblem)
+ dialogBox("ERROR",tostring(probblem),{"ok"},3,30)
+ end
+ end
+end
+
+local function rClickMenu(title,tList,tItem,posX,posY)
+
+ term.setCursorBlink(false)
+ local BoxTitle = title
+ local choices = {}
+ local termX,termY = term.getSize()
+ local offX,offY
+
+ local width = #BoxTitle + 2
+ local hight
+
+ for k,v in pairs(tList) do
+ if v ~= nil then
+ table.insert(choices,k)
+ end
+ if width < #k + 2 then
+ width = #k + 2
+ end
+ end
+
+ if #choices == 0 then
+ return
+ end
+
+ hight = #choices + 1
+ table.sort(choices)
+
+ offX,offY = math.ceil((termX/2) - (width/2)),math.ceil((termY/2) - (hight/2))
+
+ if posX and posY then -- offX,offY = posX,posY
+ if posX >= termX - width - 1 then
+ offX = termX - width - 1
+ else
+ offX = posX
+ end
+ if posY >= termY - hight then
+ offY = termY - hight
+ else
+ offY = posY
+ end
+ end
+
+ local function reDrawer()
+ printC(offX,offY,rcmTitle.txt,rcmTitle.back," "..BoxTitle..string.rep(" ",width - #BoxTitle - 1))
+ for i = 1,#choices do
+ printC(offX,offY + i,rcmList.txt,rcmList.back," "..choices[i]..string.rep(" ",width - #choices[i] - 1))
+ end
+ end
+
+ while true do
+ reDrawer()
+ local event = {os.pullEvent()}
+ if event[1] == "mouse_click" then
+ if event[2] == 1 then -- event[3] = x event[4] = y
+ if event[4] > offY and event[4] < hight + offY and event[3] >= offX and event[3] < width + offX then
+ --dialogBox("ERROR:",type(tList[choices[event[4] - offY]]),{"ok"})
+ if type(tList[choices[event[4] - offY]]) == "function" then
+ return tList[choices[event[4] - offY]](tItem)
+ elseif type(tList[choices[event[4] - offY]]) == "table" then
+ return rClickMenu("Options",tList[choices[event[4] - offY]],tItem,event[3],event[4])
+ elseif type(tList[choices[event[4] - offY]]) == "string" then
+ return osRunSpaces(
+ unpack(
+ fixArgs(
+ tList[choices[event[4] - offY]].." \""..stringPath()..fSlash..tItem.n.."\""
+ )
+ )
+ )
+ else
+ dialogBox("ERROR:","somthing up with new rMenu",{"ok"})
+ end
+ else
+ return
+ end
+ elseif event[2] == 2 then
+ return
+ end
+ end
+ end
+
+end
+
+local function preferences()
+ local tItem = {
+ {txt = "Title Bar",it = titleBar},
+ {txt = "Address Bar",it = addressBar},
+ {txt = "Item Windo", it = itemWindo},
+ {txt = "Title Right Click Title",it = rcmTitle},
+ {txt = "Right Click Menu",it = rcmList},
+ {txt = "Title Dialog Box",it = dialogTitle},
+ {txt = "Dialog Box",it = dialogWindo},
+ {txt = "Scroll Bar",it = scrollCol}
+ }
+ local topL,topR = 13,5
+ local width,hight = 23,6
+ local bottomL,bottomR = topL + width,topR + hight
+
+ local listOffset = 0
+ local sel = 1
+ local otherSel = 1
+ local otherItems = {}
+
+ if tItem[sel] then
+ for k,v in pairs(tItem[sel].it) do
+ table.insert(otherItems,{txt = k,it = v})
+ end
+ end
+
+ local function draw()
+ printC(topL,topR,titleBar.txt,titleBar.back,string.sub(" Preferences "..string.rep(" ",width),1,width))
+ for i = 0,12,4 do
+ for a = 1,4 do
+ --printC(topL + (a*12)-12 ,topR + ((i+4)/4),4,2^(a+i-1)," "..tostring(2^(a+i-1)))
+ printC(topL + a-1 ,topR + ((i+4)/4),4,2^(a+i-1)," ")
+ end
+ end
+ local sSel = " "
+ for i = 1,hight - 2 do
+ if i == sel - listOffset then
+ sSel = ">"
+ end
+ if tItem[i+listOffset] then
+ printC(topL + 4 ,topR + i,colors.black,colors.white,string.sub(sSel..tItem[i+listOffset].txt..string.rep(" ",width),1,width - 4))
+ else
+ printC(topL + 4 ,topR + i,colors.black,colors.white,sSel..string.rep(" ",width-5))
+ end
+ if i == sel - listOffset then
+ sSel = " "
+ end
+ end
+ term.setCursorPos(topL,topR + hight - 1)
+ local loop = 1
+ local length = 0
+ for i = 1,#otherItems do
+ if otherSel == i then
+ sSel = ">"
+ end
+ if colors.black == otherItems[i].it or colors.gray == otherItems[i].it then
+ term.setTextColor(colors.white)
+ else
+ term.setTextColor(colors.black)
+ end
+ term.setBackgroundColor(otherItems[i].it)
+ term.write(sSel..tostring(otherItems[i].txt).." ")
+ length = length + #otherItems[i].txt + 2
+ if otherSel == i then
+ sSel = " "
+ end
+ loop = loop+1
+ end
+ term.setBackgroundColor(colors.white)
+ term.write(string.rep(" ",width - length))
+ end
+ while true do
+ draw()
+ local event = {os.pullEvent()}
+ if event[1] == "mouse_click" and event[2] == 1 then
+ if inBouwndry(event[3],event[4],topL,topR,width,hight) then
+ local inSideX,inSideY = event[3] - topL,event[4] - topR
+ if inBouwndry(inSideX+1,inSideY,1,1,4,4) and tItem[sel] then
+ --[[
+ term.setCursorPos(1,1)
+ term.setBackgroundColor(2^(inSideX + ((inSideY*4)-4)))
+ print(2^(inSideX + ((inSideY*4)-4))," ",inSideX + ((inSideY*4)-4)," ")
+ ]]--
+ tItem[sel]["it"][otherItems[otherSel].txt] = (2^(inSideX + ((inSideY*4)-4)))
+ end
+ end
+ elseif event[1] == "key" then
+ if event[2] == 200 then
+ sel = sel - 1
+ elseif event[2] == 208 then
+ sel = sel + 1
+ elseif event[2] == 203 then
+ otherSel = otherSel - 1
+ elseif event[2] == 205 then
+ otherSel = otherSel + 1
+ elseif event[2] == 28 then
+ if dialogBox("Confirm","Save prefrences?",{"Yes","No"}) == 1 then
+ saveCFG(true)
+ end
+ return
+ end
+ end
+ if sel < 1 then
+ sel = 1
+ elseif sel > #tItem then
+ sel = #tItem
+ end
+ if sel > listOffset + hight - 2 then
+ listOffset = listOffset + 1
+ elseif sel - listOffset < 1 then
+ listOffset = listOffset - 1
+ end
+
+ otherItems = {}
+ if tItem[sel] then
+ for k,v in pairs(tItem[sel].it) do
+ table.insert(otherItems,{txt = k,it = v})
+ end
+ end
+
+ if otherSel < 1 then
+ otherSel = 1
+ elseif otherSel > #otherItems then
+ otherSel = #otherItems
+ end
+
+ if bugTest then
+ term.setBackgroundColor(colors.black)
+ term.setTextColor(colors.white)
+ term.setCursorPos(1,1)
+ term.clearLine()
+ term.write("sel "..sel.." offset "..listOffset)
+ end
+ end
+end
+
+local function fileSelect(mode) -- save_file open_file browse < not yet implemented
+
+ local title = sTitle.." "..ver
+ local bRun = true
+ local clipboard = nil
+ local cut = false
+
+ local termX,termY = term.getSize()
+ local offsetX,offsetY = 1,1
+ local hight,width = math.ceil(termY-2),math.ceil(termX-2)
+ local oldHight,oldWidth
+
+ -- offsets
+ local boxOffX,boxOffY = offsetX,offsetY + 2
+ local boxH,boxW = hight - 2 ,width - 2
+
+ local barX,barY = offsetX + 1,offsetY + 2
+ local barH,barW = 1,width - 1
+
+ local tbarX,tbarY = offsetX + 1,offsetY + 1
+ local tbarH,tbarW = 1,width - 1
+
+ local exitX,exitY = offsetX + width - 1 ,offsetY + 1
+
+ local pading = string.rep(" ",boxW)
+ local list
+
+ local listOff = 0
+
+ local sPath
+ local tItemList = {}
+
+ local function newList()
+ listOff = 0
+ flag = true
+ tItemList = {{n = "..", id = "back"}} -- adds a back item at top of list
+ sPath = stringPath()
+ local folders = {}
+ local files = {}
+ local disks = {}
+ if not fs.exists(sPath) then
+ path = {}
+ sPath = stringPath()
+ dialogBox("ERROR:","Path no longer exists",{"ok"})
+ end
+ local test,list = pcall(fs.list,sPath) -- stopes fs.list crash
+ if list == nil then
+ list = {}
+ dialogBox("ERROR : ","fs.list crashed",{"ok"})
+ end
+ if #path == 0 then
+ for i,v in pairs(rs.getSides()) do
+ if disk.isPresent(v) then
+ if disk.hasData(v) then
+ table.insert(tItemList,{n = disk.getMountPath(v), id = "disk",s = v})
+ disks[disk.getMountPath(v)] = true
+ elseif disk.hasAudio(v) then
+ table.insert(tItemList,{n = disk.getAudioTitle(v), id = "audio",s = v})
+ end
+ end
+ end
+ end
+ for i,v in pairs(list) do
+ if fs.isDir(sPath..fSlash..v) then
+ table.insert(folders,v)
+ else
+ table.insert(files,v)
+ end
+ end
+ table.sort(folders)
+ table.sort(files)
+ for i,v in pairs(folders) do
+ if disks[v] == nil then
+ table.insert(tItemList,{n = v, id = "folder"})
+ end
+ end
+ for i,v in pairs(files) do
+ table.insert(tItemList,{n = v, id = "file"})
+ end
+ end
+
+ local function paste()
+ if cut then
+ local s, m = pcall(
+ function()
+ fs.move(clipboard[1]..fSlash..clipboard[2], stringPath()..fSlash..clipboard[2])
+ cut = false
+ clipboard = nil
+ end)
+ if not s then
+ dialogBox("Error", (m or "Couldn't move"), {"ok"}, 4, 30)
+ end
+ if bugTest then
+ local x, y = term.getCursorPos()
+ term.setCursorPos(1, ({term.getSize()})[2])
+ write("from "..clipboard[1]..fSlash..clipboard[2].." to "..stringPath()..fSlash..clipboard[2])
+ end
+ else
+ local s, m = pcall(function()
+ if fs.exists(stringPath()..fSlash..clipboard[2]) then
+ fs.copy(clipboard[1]..fSlash..clipboard[2], stringPath()..fSlash.."copy-"..clipboard[2])
+ else
+ fs.copy(clipboard[1]..fSlash..clipboard[2], stringPath()..fSlash..clipboard[2])
+ end
+ end)
+ if not s then
+ dialogBox("Error", (m or "Couldn't copy"), {"ok"}, 4, 30)
+ end
+ if bugTest then
+ local x, y = term.getCursorPos()
+ term.setCursorPos(1, ({term.getSize()})[2])
+ write("from "..clipboard[1]..fSlash..clipboard[2].." to "..stringPath()..fSlash..clipboard[2])
+ end
+ end
+ newList()
+ end
+
+ -- this section bellow handles the right click menu
+
+ local tmenu = {
+ disk = {
+ ["Open"] = function(tItem)
+ table.insert(path,tItem.n)
+ newList()
+ end,
+ ["Copy"] = function(tItem)
+ clipboard = {stringPath(), tItem.n}
+ cut = false
+ end,
+ ["Eject"] = function(tItem)
+ if dialogBox("Confirm","Eject "..fSlash..tItem.n.." "..tItem.s,{"yes","no"}) == 1 then
+ disk.eject(tItem.s)
+ newList()
+ end
+ end,
+ ["ID label"] = function(tItem)
+ dialogBox("ID label",disk.getDiskID(tItem.s).." "..tostring(disk.getLabel(tItem.s)),{"ok"})
+ end,
+ ["Set label"] = function(tItem)
+ local name = InputBox("Label?")
+ if name then
+ disk.setLabel(tItem.s,name)
+ end
+ end,
+ ["Clear label"] = function(tItem)
+ if dialogBox("Confirm","Cleal Label from "..tItem.s,{"yes","no"}) == 1 then
+ disk.setLabel(tItem.s)
+ end
+ end
+ },
+ folder = {
+ ["Open"] = function(temp)
+ table.insert(path,temp.n)
+ newList()
+ end,
+ ["Copy"] = function(tItem)
+ clipboard = {stringPath(), tItem.n}
+ cut = false
+ end,
+ ["Cut"] = function(tItem)
+ clipboard = {stringPath(), tItem.n}
+ cut = true
+ end,
+ ["Delete"] = function(tItem)
+ if dialogBox("Confirm","Delete "..tItem.id.." "..tItem.n,{"yes","no"}) == 1 then
+ if fs.isReadOnly(stringPath()..fSlash..tItem.n) then
+ dialogBox("ERROR",tItem.id.." Is read Only",{"ok"})
+ else
+ fs.delete(stringPath()..fSlash..tItem.n)
+ newList()
+ end
+ end
+ end,
+ ["Rename"] = function(tItem)
+ local sName = InputBox("New Name")
+ if type(sName) == "string" and sName ~= "" then
+ local s, m = pcall(function()
+ fs.move(stringPath()..fSlash..tItem.n,stringPath()..fSlash..sName)
+ end)
+ if not s then
+ dialogBox("Error", (m or "Rename failed"), {"ok"})
+ end
+ end
+ newList()
+ end
+ },
+ file = {
+ ["Run"] = {
+ ["Run"] = function(tItem)
+ osRunSpaces(stringPath()..fSlash..tItem.n)
+ end,
+ ["Run CMD"] = function(tItem)
+ local cmd = InputBox("Commands")
+ if cmd then
+ osRunSpaces(stringPath()..fSlash..tItem.n,unpack(fixArgs(cmd)))
+ end
+ end,
+ },
+ ["Open With"] = customLaunch,
+ ["Rename"] = function(tItem)
+ local sName = InputBox("New Name")
+ if type(sName) == "string" and sName ~= "" then
+ local s, m = pcall(function()
+ fs.move(stringPath()..fSlash..tItem.n,stringPath()..fSlash..sName)
+ end)
+ if not s then
+ dialogBox("Error", (m or "Rename failed"), {"ok"})
+ end
+ end
+ newList()
+ end,
+ ["Delete"] = function(tItem)
+ if dialogBox("Confirm","Delete "..tItem.id.." "..tItem.n,{"yes","no"}) == 1 then
+ if fs.isReadOnly(stringPath()..fSlash..tItem.n) then
+ dialogBox("ERROR",tItem.id.." Is read Only",{"ok"})
+ else
+ fs.delete(stringPath()..fSlash..tItem.n)
+ newList()
+ end
+ end
+ end,
+ ["Cut"] = function(tItem)
+ clipboard = {stringPath(), tItem.n}
+ cut = true
+ end,
+ ["Copy"] = function(tItem)
+ clipboard = {stringPath(), tItem.n}
+ cut = false
+ end
+ },
+ audio = {
+ ["Play"] = 1,
+ ["Eject"] = 1
+ },
+ back = {
+ },
+ blank = { -- tmenu.blank.Paste =
+ ["Paste"] = nil,
+ ["New File"] = function()
+ local name = InputBox()
+ if name then
+ if fs.exists(stringPath()..fSlash..name) then
+ dialogBox("ERROR","Name exists",{"ok"})
+ else
+ local file = fs.open(stringPath()..fSlash..name,"w")
+ if file then
+ file.write("")
+ file.close()
+ newList()
+ else
+ dialogBox("ERROR","File not created",{"ok"})
+ end
+ end
+ end
+ end,
+ ["New Folder"] = function()
+ local name = InputBox()
+ if name then
+ if fs.exists(stringPath()..fSlash..name) then
+ dialogBox("ERROR","Name exists",{"ok"})
+ else
+ if pcall(fs.makeDir,stringPath()..fSlash..name) then
+ newList()
+ else
+ dialogBox("ERROR","Access Denied",{"ok"})
+ end
+ end
+ end
+ end,
+ ["Preferences"] = preferences
+ },
+ }
+
+ -- end right click menu
+
+ local function scrollBar(posX,posY)
+ if posX == boxOffX+boxW+1 and posY > boxOffY and posY <= boxOffY+boxH then
+ if #tItemList > boxH then
+ if posY == boxOffY + 1 then
+ listOff = 0
+ elseif posY == boxOffY+boxH then
+ listOff = #tItemList + 1 - boxH
+ else
+ listOff = math.ceil((posY - boxOffY - 1 )*(((#tItemList - boxH+2)/boxH)))
+ end
+ flag = true
+ end
+ end
+ end
+
+ newList()
+
+ while bRun do
+ if flag then
+ flag = false
+ -- clear
+ if oldHight ~= hight and oldWidth ~= width then
+ term.setBackgroundColor(colors.black)
+ term.clear()
+ oldHight,oldWidth = hight,width
+ end
+ -- draw top title bar
+ local b = tbarW - #title -2
+ if b < 0 then
+ b = 0
+ end
+ printC(tbarX,tbarY,titleBar.txt,titleBar.back,string.sub(" "..title,1,tbarW)..string.rep(" ",b))
+ term.setTextColor(colors.white)
+ term.setBackgroundColor(colors.red)
+ term.write("X")
+
+ -- draw location bar
+ local a = barW - #sPath - 1
+ if a < 0 then
+ a = 0
+ end
+ local tmppath = sPath
+ if shell and shell.getDisplayName then
+ tmppath = shell.getDisplayName(sPath)
+ --dialogBox("yay")
+ else
+ --dialogBox("moop")
+ end
+ tmppath = tmppath or sPath
+ local a = barW - #tmppath - 1
+ if a < 0 then
+ a = 0
+ end
+ printC(barX,barY,addressBar.txt,addressBar.back,string.sub(" "..tmppath,1,barW)..string.rep(" ",a))
+
+ -- draw scroll bar
+ if #tItemList > boxH then
+ term.setBackgroundColor(scrollCol.back)
+ for i = 1,boxH do
+ term.setCursorPos(boxOffX+boxW+1,i + boxOffY)
+ local scroll = math.floor( boxH* (listOff/(#tItemList-boxH+2)) )+1
+ if i == scroll then
+ term.setBackgroundColor(scrollCol.button)
+ term.write(" ")
+ term.setBackgroundColor(scrollCol.back)
+ else
+ term.write(" ")
+ end
+ end
+ else
+ term.setBackgroundColor(scrollCol.off)
+ for i = 1,boxH do
+ term.setCursorPos(boxOffX+boxW+1,i + boxOffY)
+ term.write(" ")
+ end
+ end
+
+ -- draw main section
+
+ for i = 1,boxH do -- listOff
+ local sel = i+listOff
+ if tItemList[sel] then
+ printC(1+boxOffX,i+boxOffY,(tIcons[tItemList[sel].id].tCol or itemWindo.txt),(tIcons[tItemList[sel].id].bCol or itemWindo.back),( tIcons[tItemList[sel].id].txt or " "))
+ printC(4+boxOffX,i+boxOffY,itemWindo.txt,itemWindo.back,string.sub(" "..tItemList[sel].n..pading,1,boxW-3))
+ else
+ printC(1+boxOffX,i+boxOffY,itemWindo.txt,itemWindo.back,pading)
+ end
+ end
+
+ if bugTest then
+ printC(1,1,"black","white",listOff.." "..boxOffY.." "..boxH)
+ end
+
+ end
+
+ -- react to events
+ local event = {os.pullEvent()}
+
+ if event[1] == "mouse_click" then
+ if inBouwndry(event[3],event[4],boxOffX+1,boxOffY+1,boxW,boxH) then
+ local selected = tItemList[event[4]+listOff-boxOffY]
+ if selected and inBouwndry(event[3],event[4],boxOffX+1,event[4],#selected.n + 4,1) then
+ if event[2] == 1 then -- left mouse
+ if selected.id == "back" then
+ table.remove(path,#path)
+ newList()
+ elseif selected.id == "folder" or selected.id == "disk" then
+ table.insert(path,selected.n)
+ newList()
+ elseif selected.id == "file" then
+ if dialogBox("Run file ?",selected.n,{"yes","no"}) == 1 then
+ osRunSpaces(stringPath()..fSlash..selected.n)
+ end
+ flag = true
+ end
+ elseif event[2] == 2 then -- right mouse
+ rClickMenu("Options",tmenu[selected.id],selected,event[3],event[4])
+ flag = true
+ end
+ elseif event[2] == 2 then -- right clicking not on object
+ if clipboard then
+ tmenu.blank.Paste = paste
+ else
+ tmenu.blank.Paste = nil
+ end
+ rClickMenu("Options",tmenu["blank"],selected,event[3],event[4])
+ flag = true
+ end
+ elseif event[2] == 1 and event[3] == exitX and event[4] == exitY then
+ if dialogBox("Confirm","Exit application",{"yes","no"}) == 1 then
+ bRun = false
+ end
+ flag = true
+ elseif event[2] == 1 then
+ scrollBar(event[3],event[4])
+ end
+ elseif event[1] == "mouse_scroll" then -- flag this needs new math
+ local old = listOff
+ listOff = listOff + event[2]
+ if listOff < 0 then
+ listOff = 0
+ end
+ if #tItemList + 1 - boxH > 0 and listOff > #tItemList + 1 - boxH then
+ listOff = #tItemList + 1 - boxH
+ elseif listOff > 0 and #tItemList + 1 - boxH < 0 then
+ listOff = 0
+ end
+ if listOff ~= old then
+ flag = true
+ end
+
+ elseif event[1] == "mouse_drag" then -- scroll bar
+ scrollBar(event[3],event[4])
+ elseif event[1] == "disk" or event[1] == "disk_eject" then
+ newList()
+ elseif event[1] == "window_resize" then
+ termX,termY = term.getSize()
+ offsetX,offsetY = 1,1
+ hight,width = math.ceil(termY-2),math.ceil(termX-2)
+
+ boxOffX,boxOffY = offsetX,offsetY + 2
+ boxH,boxW = hight - 2 ,width - 2
+
+ barX,barY = offsetX + 1,offsetY + 2
+ barH,barW = 1,width - 1
+
+ tbarX,tbarY = offsetX + 1,offsetY + 1
+ tbarH,tbarW = 1,width - 1
+
+ exitX,exitY = offsetX + width - 1 ,offsetY + 1
+ pading = string.rep(" ",boxW)
+
+ flag = true
+ elseif event[1] == "redraw" then
+ flag = true
+ end
+ end
+end
+
+local function main()
+ if term.isColor() then
+ clear()
+ fileSelect()
+ clear()
+ else
+ error("Not an Advanced Computer (gold) ")
+ end
+end
+
+local trash = (norun or main()) \ No newline at end of file
diff --git a/programs/mousebrowser.shortcut b/programs/mousebrowser.shortcut
new file mode 100644
index 0000000..a3713ab
--- /dev/null
+++ b/programs/mousebrowser.shortcut
@@ -0,0 +1,2 @@
+name: File Browser
+shortcut: /programs/mousebrowser.lua \ No newline at end of file
diff --git a/programs/npaintpro.lua b/programs/npaintpro.lua
new file mode 100644
index 0000000..56f7ef0
--- /dev/null
+++ b/programs/npaintpro.lua
@@ -0,0 +1,2827 @@
+--[[
+ NPaintPro
+ By NitrogenFingers
+]]--
+
+--The screen size
+local w,h = term.getSize()
+--Whether or not the program is currently waiting on user input
+local inMenu = false
+--Whether or not a drop down menu is active
+local inDropDown = false
+--Whether or not animation tools are enabled (use -a to turn them on)
+local animated = false
+--Whether or not the text tools are enabled (use -t to turn them on)
+local textual = false
+--Whether or not "blueprint" display mode is on
+local blueprint = false
+--Whether or not the "layer" display is on
+local layerDisplay = false
+--Whether or not the interface is presently hidden
+local interfaceHidden = false
+--Whether or not the "direction" display is on
+local printDirection = false
+--The tool/mode npaintpro is currently in. Default is "paint"
+--For a list of modes, check out the help file
+local state = "paint"
+--Whether or not the program is presently running
+local isRunning = true
+--The rednet address of the 3D printer, if one has been attached
+local printer = nil
+
+--The list of every frame, containing every image in the picture/animation
+--Note: nfp files always have the picture at frame 1
+local frames = { }
+--How many frames are currently in the given animation.
+local frameCount = 1
+--The Colour Picker column
+local column = {}
+--The offset of visible colours in the picker column, if the screen cannot fit all 16
+local columnoffset = 0
+--The currently selected left and right colours
+local lSel,rSel = colours.white,nil
+--The amount of scrolling on the X and Y axis
+local sx,sy = 0,0
+--The alpha channel colour
+--Change this to change default canvas colour
+local alphaC = colours.black
+--The currently selected frame. Default is 1
+local sFrame = 1
+--The contents of the image buffer- contains contents, width and height
+local buffer = nil
+--The position, width and height of the selection rectangle
+local selectrect = nil
+
+--Whether or not text tools are enabled for this document
+local textEnabled = false
+--The X and Y positions of the text cursor
+local textCurX, textCurY = 1,1
+
+--The currently calculated required materials
+local requiredMaterials = {}
+--Whether or not required materials are being displayed in the pallette
+local requirementsDisplayed = false
+--A list of the rednet ID's all in-range printers located
+local printerList = { }
+--A list of the names of all in-range printers located. Same as the printerList in reference
+local printerNames = { }
+--The selected printer
+local selectedPrinter = 1
+--The X,Y,Z and facing of the printer
+local px,py,pz,pfx,pfz = 0,0,0,0,0
+--The form of layering used
+local layering = "up"
+
+--The animation state of the selection rectangle and image buffer
+local rectblink = 0
+--The ID for the timer
+local recttimer = nil
+--The radius of the brush tool
+local brushsize = 3
+--Whether or not "record" mode is activated (animation mode only)
+local record = false
+--The time between each frame when in play mode (animation mode only)
+local animtime = 0.3
+
+--The current "cursor position" in text mode
+local cursorTexX,cursorTexY = 1,1
+
+--A list of hexidecimal conversions from numbers to hex digits
+local hexnums = { [10] = "a", [11] = "b", [12] = "c", [13] = "d", [14] = "e" , [15] = "f" }
+--The NPaintPro logo (divine, isn't it?)
+local logo = {
+"fcc 3 339";
+" fcc 9333 33";
+" fcc 933 333 33";
+" fcc 933 33 33";
+" fcc 933 33 33";
+" c88 333 93333";
+" 888 333 9333";
+" 333 3 333 939";
+}
+--The Layer Up and Layer Forward printing icons
+local layerUpIcon = {
+ "0000000";
+ "0088880";
+ "0888870";
+ "07777f0";
+ "0ffff00";
+ "0000000";
+}
+local layerForwardIcon = {
+ "0000000";
+ "000fff0";
+ "00777f0";
+ "0888700";
+ "0888000";
+ "0000000";
+}
+--The available menu options in the ctrl menu
+local mChoices = {"Save","Exit"}
+--The available modes from the dropdown menu- tables indicate submenus (include a name!)
+local ddModes = { { "paint", "brush", "pippette", "flood", "move", "clear", "select", name = "painting" }, { "alpha to left", "alpha to right", "hide interface", name = "display" }, "help", { "print", "save", "exit", name = "file" }, name = "menu" }
+--The available modes from the selection right-click menu
+local srModes = { "cut", "copy", "paste", "clear", "hide", name = "selection" }
+--The list of available help topics for each mode 127
+local helpTopics = {
+ [1] = {
+ name = "Paint Mode",
+ key = nil,
+ animonly = false,
+ textonly = false,
+ message = "The default mode for NPaintPro, for painting pixels."
+ .." Controls here that are not overridden will apply for all other modes. Leaving a mode by selecting that mode "
+ .." again will always send the user back to paint mode.",
+ controls = {
+ { "Arrow keys", "Scroll the canvas" },
+ { "Left Click", "Paint/select left colour" },
+ { "Right Click", "Paint/select right colour" },
+ { "Z Key", "Clear image on screen" },
+ { "Tab Key", "Hide selection rectangle if visible" },
+ { "Q Key", "Set alpha mask to left colour" },
+ { "W Key", "Set alpha mask to right colour" },
+ { "Number Keys", "Swich between frames 1-9" },
+ { "</> keys", "Move to the next/last frame" },
+ { "R Key", "Removes every frame after the current frame"}
+ }
+ },
+ [2] = {
+ name = "Brush Mode",
+ key = "b",
+ animonly = false,
+ textonly = false,
+ message = "Brush mode allows painting a circular area of variable diameter rather than a single pixel, working in "..
+ "the exact same way as paint mode in all other regards.",
+ controls = {
+ { "Left Click", "Paints a brush blob with the left colour" },
+ { "Right Click", "Paints a brush blob with the right colour" },
+ { "Number Keys", "Changes the radius of the brush blob from 2-9" }
+ }
+ },
+ [3] = {
+ name = "Pippette Mode",
+ key = "p",
+ animonly = false,
+ textonly = false,
+ message = "Pippette mode allows the user to click the canvas and set the colour clicked to the left or right "..
+ "selected colour, for later painting.",
+ controls = {
+ { "Left Click", "Sets clicked colour to the left selected colour" },
+ { "Right Click", "Sets clicked colour to the right selected colour" }
+ }
+ },
+ [4] = {
+ name = "Move Mode",
+ key = "m",
+ animonly = false,
+ textonly = false,
+ message = "Mode mode allows the moving of the entire image on the screen. This is especially useful for justifying"..
+ " the image to the top-left for animations or game assets.",
+ controls = {
+ { "Left/Right Click", "Moves top-left corner of image to selected square" },
+ { "Arrow keys", "Moves image one pixel in any direction" }
+ }
+ },
+ [5] = {
+ name = "Flood Mode",
+ key = "f",
+ animonly = false,
+ textonly = false,
+ message = "Flood mode allows the changing of an area of a given colour to that of the selected colour. "..
+ "The tool uses a flood4 algorithm and will not fill diagonally. Transparency cannot be flood filled.",
+ controls = {
+ { "Left Click", "Flood fills selected area to left colour" },
+ { "Right Click", "Flood fills selected area to right colour" }
+ }
+ },
+ [6] = {
+ name = "Select Mode",
+ key = "s",
+ animonly = false,
+ textonly = false,
+ message = "Select mode allows the creation and use of the selection rectangle, to highlight specific areas on "..
+ "the screen and perform operations on the selected area of the image. The selection rectangle can contain an "..
+ "image on the clipboard- if it does, the image will flash inside the rectangle, and the rectangle edges will "..
+ "be light grey instead of dark grey.",
+ controls = {
+ { "C Key", "Copy: Moves selection into the clipboard" },
+ { "X Key", "Cut: Clears canvas under the rectangle, and moves it into the clipboard" },
+ { "V Key", "Paste: Copys clipboard to the canvas" },
+ { "Z Key", "Clears clipboard" },
+ { "Left Click", "Moves top-left corner of rectangle to selected pixel" },
+ { "Right Click", "Opens selection menu" },
+ { "Arrow Keys", "Moves rectangle one pixel in any direction" }
+ }
+ },
+ [7] = {
+ name = "Corner Select Mode",
+ key = nil,
+ animonly = false,
+ textonly = false,
+ message = "If a selection rectangle isn't visible, this mode is selected automatically. It allows the "..
+ "defining of the corners of the rectangle- one the top-left and bottom-right corners have been defined, "..
+ "NPaintPro switches to selection mode. Note rectangle must be at least 2 pixels wide and high.",
+ controls = {
+ { "Left/Right Click", "Defines a corner of the selection rectangle" }
+ }
+ },
+ [8] = {
+ name = "Play Mode",
+ key = "space",
+ animonly = true,
+ textonly = false,
+ message = "Play mode will loop through each frame in your animation at a constant rate. Editing tools are "..
+ "locked in this mode, and the coordinate display will turn green to indicate it is on.",
+ controls = {
+ { "</> Keys", "Increases/Decreases speed of the animation" },
+ { "Space Bar", "Returns to paint mode" }
+ }
+ },
+ [9] = {
+ name = "Record Mode",
+ key = "\\",
+ animonly = true,
+ textonly = false,
+ message = "Record mode is not a true mode, but influences how other modes work. Changes made that modify the "..
+ "canvas in record mode will affect ALL frames in the animation. The coordinates will turn red to indicate that "..
+ "record mode is on.",
+ controls = {
+ { "", "Affects:" },
+ { "- Paint Mode", "" },
+ { "- Brush Mode", "" },
+ { "- Cut and Paste in Select Mode", ""},
+ { "- Move Mode", ""}
+ }
+ },
+ [10] = {
+ name = "Hide Interface",
+ key = "~",
+ animonly = false,
+ textonly = false,
+ message = "Hides the sidebar and colour picker so only the image is visible."..
+ " The program can be started with the interface hidden using the -d command line option."..
+ " When hidden, if a file is animated it will automatically go to play mode.\n"..
+ "Note that all other input is locked until the display is revealed again in this"..
+ " mode.",
+ controls = {
+ { "</> Keys", "Increases/Decreases speed of the animation" },
+ { "~ Key", "Shows interface"}
+ }
+ },
+ [11] = {
+ name = "Help Mode",
+ key = "h",
+ animonly = false,
+ textonly = false,
+ message = "Displays this help screen. Clicking on options will display help on that topic. Clicking out of the screen"..
+ " will leave this mode.",
+ controls = {
+ { "Left/Right Click", "Displays a topic/Leaves the mode" }
+ }
+ },
+ [12] = {
+ name = "File Mode",
+ key = nil,
+ animonly = false,
+ textonly = false,
+ message = "Clicking on the mode display at the bottom of the screen will open the options menu. Here you can"..
+ " activate all of the modes in the program with a simple mouse click. Pressing left control will open up the"..
+ " file menu automatically.",
+ controls = {
+ { "leftCtrl", "Opens the file menu" },
+ { "leftAlt", "Opens the paint menu" }
+ }
+ },
+ [13] = {
+ name = "Text Mode",
+ key = "t",
+ animonly = false,
+ textonly = true,
+ message = "In this mode, the user is able to type letters onto the document for display. The left colour "..
+ "pallette value determines what colour the text will be, and the right determines what colour the background "..
+ "will be (set either to nil to keep the same colours as already there).",
+ controls = {
+ { "Backspace", "Deletes the character on the previous line" },
+ { "Arrow Keys", "Moves the cursor in any direction" },
+ { "Left Click", "Moves the cursor to beneath the mouse cursor" }
+ }
+ },
+ [14] = {
+ name = "Textpaint Mode",
+ key = "y",
+ animonly = false,
+ textonly = true,
+ message = "Allows the user to paint any text on screen to the desired colour with the mouse. If affects the text colour"..
+ " values rather than the background values, but operates identically to paint mode in all other regards.",
+ controls = {
+ { "Left Click", "Paints the text with the left colour" },
+ { "Right Click", "Paints the text with the right colour" }
+ }
+ },
+ [15] = {
+ name = "About NPaintPro",
+ keys = nil,
+ animonly = false,
+ textonly = false,
+ message = "NPaintPro: The feature-bloated paint program for ComputerCraft by Nitrogen Fingers.",
+ controls = {
+ { "Testers:", " "},
+ { " ", "Faubiguy"},
+ { " ", "TheOriginalBIT"}
+ }
+ }
+}
+--The "bounds" of the image- the first/last point on both axes where a pixel appears
+local toplim,botlim,leflim,riglim = nil,nil,nil,nil
+--The selected path
+local sPath = nil
+
+
+--Screen Size Parameters- decided dynamically further down the program
+--Whether or not the help screen is available
+local helpAvailable = true
+--Whether or not the main menu is available
+local mainAvailable = true
+--Whether or not selection box dropdowns are available
+local boxdropAvailable = true
+--Whether or not a manual file descriptor option is available (part of the title)
+local filemakerAvailable = true
+
+--[[
+ Section: Helpers
+]]--
+
+--[[Converts a colour parameter into a single-digit hex coordinate for the colour
+ Params: colour:int = The colour to be converted
+ Returns:string A string conversion of the colour
+]]--
+local function getHexOf(colour)
+ if not colour or not tonumber(colour) then
+ return " "
+ end
+ local value = math.log(colour)/math.log(2)
+ if value > 9 then
+ value = hexnums[value]
+ end
+ return value
+end
+
+--[[Converts a hex digit into a colour value
+ Params: hex:?string = the hex digit to be converted
+ Returns:string A colour value corresponding to the hex, or nil if the character is invalid
+]]--
+local function getColourOf(hex)
+ local value = tonumber(hex, 16)
+ if not value then return nil end
+ value = math.pow(2,value)
+ return value
+end
+
+--[[Finds the largest width and height of the text in a given menu. Should conform to the format
+ of all standard menus (number indexed values and a 'name' field).
+ This is done recursively. It's just easier that way.
+ Params: menu:table = the table being tested for the max width and height
+ Returns:number,number = the max width and height of the text or names of any menu or submenu.
+]]--
+local function findMaxWH(menu)
+ local wmax,hmax = #menu.name, #menu
+ for _,entry in pairs(menu) do
+ if type(entry) == "table" then
+ local nw,nh = findMaxWH(entry)
+ wmax = math.max(wmax,nw)
+ hmax = math.max(hmax,nh)
+ else
+ wmax = math.max(wmax,#entry)
+ end
+ end
+ return wmax,hmax
+end
+
+--[[Determines what services are available depending on the size of the screen. Certain features
+ may be disabled with screen real estate does not allow for it.
+ Params: none
+ Returns:nil
+]]--
+local function determineAvailableServices()
+ --Help files were designed to fit a 'standard' CC screen, of 51 x 19. The height of the screen
+ --needs to match the number of available options plus white space, but for consistency with
+ --the files themselves, a natural size of 51 is required for the screen width as well.
+ if w < 51 or h < #helpTopics+3 then helpAvailable = false end
+ if not helpAvailable then table.remove(ddModes,3) end
+ --These hard-coded values mirror the drawLogo values, with extra consideration for the
+ --additional menu options
+ if h < 14 or w < 24 then filemakerAvailable = false end
+
+ --Menus can't cover the picker and need 2 spaces for branches. 4 whitespace on X total.
+ --Menus need a title and can't eclipse the footer. 2 whitespace on Y total.
+ local wmin,hmin = findMaxWH(ddModes)
+ if w < wmin+4 or h < hmin+2 then mainAvailable = false end
+ wmin,hmin = findMaxWH(srModes)
+ if w < wmin+4 or h < hmin+2 then boxdropAvailable = false end
+end
+
+--[[Finds the biggest and smallest bounds of the image- the outside points beyond which pixels do not appear
+ These values are assigned to the "lim" parameters for access by other methods
+ Params: forAllFrames:bool = True if all frames should be used to find bounds, otherwise false or nil
+ Returns:nil
+]]--
+local function updateImageLims(forAllFrames)
+ local f,l = sFrame,sFrame
+ if forAllFrames == true then f,l = 1,framecount end
+
+ toplim,botlim,leflim,riglim = nil,nil,nil,nil
+ for locf = f,l do
+ for y,_ in pairs(frames[locf]) do
+ if type(y) == "number" then
+ for x,_ in pairs(frames[locf][y]) do
+ if frames[locf][y][x] ~= nil then
+ if leflim == nil or x < leflim then leflim = x end
+ if toplim == nil or y < toplim then toplim = y end
+ if riglim == nil or x > riglim then riglim = x end
+ if botlim == nil or y > botlim then botlim = y end
+ end
+ end
+ end
+ end
+ end
+
+ --There is just... no easier way to do this. It's horrible, but necessary
+ if textEnabled then
+ for locf = f,l do
+ for y,_ in pairs(frames[locf].text) do
+ for x,_ in pairs(frames[locf].text[y]) do
+ if frames[locf].text[y][x] ~= nil then
+ if leflim == nil or x < leflim then leflim = x end
+ if toplim == nil or y < toplim then toplim = y end
+ if riglim == nil or x > riglim then riglim = x end
+ if botlim == nil or y > botlim then botlim = y end
+ end
+ end
+ end
+ for y,_ in pairs(frames[locf].textcol) do
+ for x,_ in pairs(frames[locf].textcol[y]) do
+ if frames[locf].textcol[y][x] ~= nil then
+ if leflim == nil or x < leflim then leflim = x end
+ if toplim == nil or y < toplim then toplim = y end
+ if riglim == nil or x > riglim then riglim = x end
+ if botlim == nil or y > botlim then botlim = y end
+ end
+ end
+ end
+ end
+ end
+end
+
+--[[Determines how much of each material is required for a print. Done each time printing is called.
+ Params: none
+ Returns:table A complete list of how much of each material is required.
+]]--
+function calculateMaterials()
+ updateImageLims(animated)
+ requiredMaterials = {}
+ for i=1,16 do
+ requiredMaterials[i] = 0
+ end
+
+ if not toplim then return end
+
+ for i=1,#frames do
+ for y = toplim, botlim do
+ for x = leflim, riglim do
+ if type(frames[i][y][x]) == "number" then
+ requiredMaterials[math.log10(frames[i][y][x])/math.log10(2) + 1] =
+ requiredMaterials[math.log10(frames[i][y][x])/math.log10(2) + 1] + 1
+ end
+ end
+ end
+ end
+end
+
+
+--[[Updates the rectangle blink timer. Should be called anywhere events are captured, along with a timer capture.
+ Params: nil
+ Returns:nil
+]]--
+local function updateTimer(id)
+ if id == recttimer then
+ recttimer = os.startTimer(0.5)
+ rectblink = (rectblink % 2) + 1
+ end
+end
+
+--[[Constructs a message based on the state currently selected
+ Params: nil
+ Returns:string A message regarding the state of the application
+]]--
+local function getStateMessage()
+ local msg = " "..string.upper(string.sub(state, 1, 1))..string.sub(state, 2, #state).." mode"
+ if state == "brush" then msg = msg..", size="..brushsize end
+ return msg
+end
+
+--[[Calls the rednet_message event, but also looks for timer events to keep then
+ system timer ticking.
+ Params: timeout:number how long before the event times out
+ Returns:number the id of the sender
+ :number the message send
+]]--
+local function rsTimeReceive(timeout)
+ local timerID
+ if timeout then timerID = os.startTimer(timeout) end
+
+ local id,key,msg = nil,nil
+ while true do
+ id,key,msg = os.pullEvent()
+
+ if id == "timer" then
+ if key == timerID then return
+ else updateTimer(key) end
+ end
+ if id == "rednet_message" then
+ return key,msg
+ end
+ end
+end
+
+--[[Draws a picture, in paint table format on the screen
+ Params: image:table = the image to display
+ xinit:number = the x position of the top-left corner of the image
+ yinit:number = the y position of the top-left corner of the image
+ alpha:number = the color to display for the alpha channel. Default is white.
+ Returns:nil
+]]--
+local function drawPictureTable(image, xinit, yinit, alpha)
+ if not alpha then alpha = 1 end
+ for y=1,#image do
+ for x=1,#image[y] do
+ term.setCursorPos(xinit + x-1, yinit + y-1)
+ local col = getColourOf(string.sub(image[y], x, x))
+ if not col then col = alpha end
+ term.setBackgroundColour(col)
+ term.write(" ")
+ end
+ end
+end
+
+--[[
+ Section: Loading
+]]--
+
+--[[Loads a non-animted paint file into the program
+ Params: path:string = The path in which the file is located
+ Returns:nil
+]]--
+local function loadNFP(path)
+ sFrame = 1
+ frames[sFrame] = { }
+ if fs.exists(path) then
+ local file = io.open(path, "r" )
+ local sLine = file:read()
+ local num = 1
+ while sLine do
+ table.insert(frames[sFrame], num, {})
+ for i=1,#sLine do
+ frames[sFrame][num][i] = getColourOf(string.sub(sLine,i,i))
+ end
+ num = num+1
+ sLine = file:read()
+ end
+ file:close()
+ end
+end
+
+--[[Loads a text-paint file into the program
+ Params: path:string = The path in which the file is located
+ Returns:nil
+]]--
+local function loadNFT(path)
+ sFrame = 1
+ frames[sFrame] = { }
+ frames[sFrame].text = { }
+ frames[sFrame].textcol = { }
+
+ if fs.exists(path) then
+ local file = io.open(path, "r")
+ local sLine = file:read()
+ local num = 1
+ while sLine do
+ table.insert(frames[sFrame], num, {})
+ table.insert(frames[sFrame].text, num, {})
+ table.insert(frames[sFrame].textcol, num, {})
+
+ --As we're no longer 1-1, we keep track of what index to write to
+ local writeIndex = 1
+ --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour
+ local bgNext, fgNext = false, false
+ --The current background and foreground colours
+ local currBG, currFG = nil,nil
+ term.setCursorPos(1,1)
+ for i=1,#sLine do
+ local nextChar = string.sub(sLine, i, i)
+ if nextChar:byte() == 30 then
+ bgNext = true
+ elseif nextChar:byte() == 31 then
+ fgNext = true
+ elseif bgNext then
+ currBG = getColourOf(nextChar)
+ bgNext = false
+ elseif fgNext then
+ currFG = getColourOf(nextChar)
+ fgNext = false
+ else
+ if nextChar ~= " " and currFG == nil then
+ currFG = colours.white
+ end
+ frames[sFrame][num][writeIndex] = currBG
+ frames[sFrame].textcol[num][writeIndex] = currFG
+ frames[sFrame].text[num][writeIndex] = nextChar
+ writeIndex = writeIndex + 1
+ end
+ end
+ num = num+1
+ sLine = file:read()
+ end
+ file:close()
+ end
+end
+
+--[[Loads an animated paint file into the program
+ Params: path:string = The path in which the file is located
+ Returns:nil
+]]--
+local function loadNFA(path)
+ frames[sFrame] = { }
+ if fs.exists(path) then
+ local file = io.open(path, "r" )
+ local sLine = file:read()
+ local num = 1
+ while sLine do
+ table.insert(frames[sFrame], num, {})
+ if sLine == "~" then
+ sFrame = sFrame + 1
+ frames[sFrame] = { }
+ num = 1
+ else
+ for i=1,#sLine do
+ frames[sFrame][num][i] = getColourOf(string.sub(sLine,i,i))
+ end
+ num = num+1
+ end
+ sLine = file:read()
+ end
+ file:close()
+ end
+ framecount = sFrame
+ sFrame = 1
+end
+
+--[[Saves a non-animated paint file to the specified path
+ Params: path:string = The path to save the file to
+ Returns:nil
+]]--
+local function saveNFP(path)
+ local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath))
+ if not fs.exists(sDir) then
+ fs.makeDir(sDir)
+ end
+
+ local file = io.open(path, "w")
+ updateImageLims(false)
+ if not toplim then
+ file:close()
+ return
+ end
+ for y=1,botlim do
+ local line = ""
+ if frames[sFrame][y] then
+ for x=1,riglim do
+ line = line..getHexOf(frames[sFrame][y][x])
+ end
+ end
+ file:write(line.."\n")
+ end
+ file:close()
+end
+
+--[[Saves a text-paint file to the specified path
+ Params: path:string = The path to save the file to
+ Returns:nil
+]]--
+local function saveNFT(path)
+ local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath))
+ if not fs.exists(sDir) then
+ fs.makeDir(sDir)
+ end
+
+ local file = io.open(path, "w")
+ updateImageLims(false)
+ if not toplim then
+ file:close()
+ return
+ end
+ for y=1,botlim do
+ local line = ""
+ local currBG, currFG = nil,nil
+ for x=1,riglim do
+ if frames[sFrame][y] and frames[sFrame][y][x] ~= currBG then
+ line = line..string.char(30)..getHexOf(frames[sFrame][y][x])
+ currBG = frames[sFrame][y][x]
+ end
+ if frames[sFrame].textcol[y] and frames[sFrame].textcol[y][x] ~= currFG then
+ line = line..string.char(31)..getHexOf(frames[sFrame].textcol[y][x])
+ currFG = frames[sFrame].textcol[y][x]
+ end
+ if frames[sFrame].text[y] then
+ local char = frames[sFrame].text[y][x]
+ if not char then char = " " end
+ line = line..char
+ end
+ end
+ file:write(line.."\n")
+ end
+ file:close()
+end
+
+--[[Saves a animated paint file to the specified path
+ Params: path:string = The path to save the file to
+ Returns:nil
+]]--
+local function saveNFA(path)
+ local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath))
+ if not fs.exists(sDir) then
+ fs.makeDir(sDir)
+ end
+
+ local file = io.open(path, "w")
+ updateImageLims(true)
+ if not toplim then
+ file:close()
+ return
+ end
+ for i=1,#frames do
+ for y=1,botlim do
+ local line = ""
+ if frames[i][y] then
+ for x=1,riglim do
+ line = line..getHexOf(frames[i][y][x])
+ end
+ end
+ file:write(line.."\n")
+ end
+ if i < #frames then file:write("~\n") end
+ end
+ file:close()
+end
+
+--[[Runs a special pre-program dialogue to determine the filename and filepath. Done if
+ there's room, and a file name hasn't been specified
+ Params: none
+ Returns:bool= true if file is created; false otherwise
+]]--
+local function runFileMaker()
+ local newFName = ""
+ local fileType = 1
+ if animated then fileType = 2
+ elseif textEnabled then fileType = 3 end
+
+ local tlx,tly = math.floor(w/2 - #logo[1]/2), math.floor(h/2 + #logo/2 + 1)
+
+ --This is done on top of the logo, so it backpedals a bit.
+ term.setCursorPos(tlx, tly)
+ term.clearLine()
+ term.write("Name: ")
+ term.setCursorPos(tlx, tly + 1)
+ term.clearLine()
+ term.write("Filetype: Sprite")
+ term.setCursorPos(tlx + 12, tly + 2)
+ term.write("Animation")
+ term.setCursorPos(tlx + 12, tly + 3)
+ term.write("Text")
+
+ while true do
+ term.setCursorPos(tlx + 6, tly)
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ term.write(newFName..string.rep(" ", 15-#newFName))
+ term.setBackgroundColour(colours.white)
+ term.setTextColour(colours.black)
+ local extension = ".nfp"
+ if fileType == 2 then extension = ".nfa"
+ elseif fileType == 3 then extension = ".nft" end
+ term.write(extension)
+
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ for i=1,3 do
+ term.setCursorPos(tlx + 24, tly + i)
+ if i==fileType then term.write("X")
+ else term.write(" ") end
+ end
+
+ local fPath = shell.resolve(newFName..extension)
+ term.setCursorPos(tlx, tly + 3)
+ local fileValid = true
+ if (fs.exists(fPath) and fs.isDir(fPath)) or newFName == "" then
+ term.setBackgroundColour(colours.white)
+ term.setTextColour(colours.red)
+ term.write("Invalid ")
+ fileValid = false
+ elseif fs.exists(fPath) then
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ term.write(" Edit ")
+ else
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ term.write(" Create ")
+ end
+
+ term.setTextColour(colours.grey)
+ term.setCursorPos(tlx + 6 + #newFName, tly)
+ term.setCursorBlink(true)
+
+ local id,p1,p2,p3 = os.pullEvent()
+ if id == "key" then
+ if p1 == keys.backspace and #newFName > 0 then
+ newFName = string.sub(newFName, 1, #newFName-1)
+ elseif p1 == keys.enter and fileValid then
+ sPath = fPath
+ return true
+ end
+ elseif id == "char" and p1 ~= "." and p1 ~= " " and #newFName < 15 then
+ newFName = newFName..p1
+ elseif id == "mouse_click" then
+ --The option boxes. Man, hardcoding is ugly...
+ if p2 == tlx + 24 then
+ for i=1,3 do
+ if p3 == tly+i then fileType = i end
+ end
+ end
+ if p3 == tly + 3 and p2 >= tlx and p2 <= tlx + 8 and fileValid then
+ sPath = fPath
+ return true
+ end
+ end
+ end
+end
+
+--[[Initializes the program, by loading in the paint file. Called at the start of each program.
+ Params: none
+ Returns:nil
+]]--
+local function init()
+ if textEnabled then
+ loadNFT(sPath)
+ table.insert(ddModes, 2, { "text", "textpaint", name = "text"})
+ elseif animated then
+ loadNFA(sPath)
+ table.insert(ddModes, #ddModes, { "record", "play", name = "anim" })
+ table.insert(ddModes, #ddModes, { "go to", "remove", name = "frames"})
+ table.insert(ddModes[2], #ddModes[2], "blueprint on")
+ table.insert(ddModes[2], #ddModes[2], "layers on")
+ else
+ loadNFP(sPath)
+ table.insert(ddModes[2], #ddModes[2], "blueprint on")
+ end
+
+ for i=0,15 do
+ table.insert(column, math.pow(2,i))
+ end
+end
+
+--[[
+ Section: Drawing
+]]--
+
+
+--[[Draws the rather superflous logo. Takes about 1 second, before user is able to move to the
+ actual program.
+ Params: none
+ Returns:bool= true if the file select ran successfully; false otherwise.
+]]--
+local function drawLogo()
+ term.setBackgroundColour(colours.white)
+ term.clear()
+ if h >= 12 and w >= 24 then
+ drawPictureTable(logo, w/2 - #logo[1]/2, h/2 - #logo/2, colours.white)
+ term.setBackgroundColour(colours.white)
+ term.setTextColour(colours.black)
+ local msg = "NPaintPro"
+ term.setCursorPos(w/2 - #msg/2, h/2 + #logo/2 + 1)
+ term.write(msg)
+ msg = "By NitrogenFingers"
+ term.setCursorPos(w/2 - #msg/2, h/2 + #logo/2 + 2)
+ term.write(msg)
+ elseif w >= 15 then
+ local msg = "NPaintPro"
+ term.setCursorPos(math.ceil(w/2 - #msg/2), h/2)
+ term.setTextColour(colours.cyan)
+ term.write(msg)
+ msg = "NitrogenFingers"
+ term.setCursorPos(math.ceil(w/2 - #msg/2), h/2 + 1)
+ term.setTextColour(colours.black)
+ term.write(msg)
+ else
+ local msg = "NPP"
+ term.setCursorPos(math.ceil(w/2 - #msg/2), math.floor(h/2))
+ term.setTextColour(colours.cyan)
+ term.write(msg)
+ msg = "By NF"
+ term.setCursorPos(math.ceil(w/2 - #msg/2), math.ceil(h/2))
+ term.setTextColour(colours.black)
+ term.write(msg)
+ end
+ os.pullEvent()
+end
+
+--[[Clears the display to the alpha channel colour, draws the canvas, the image buffer and the selection
+ rectanlge if any of these things are present.
+ Params: none
+ Returns:nil
+]]--
+local function drawCanvas()
+ --We have to readjust the position of the canvas if we're printing
+ turtlechar = "@"
+ if state == "active print" then
+ if layering == "up" then
+ if py >= 1 and py <= #frames then
+ sFrame = py
+ end
+ if pz < sy then sy = pz
+ elseif pz > sy + h - 1 then sy = pz + h - 1 end
+ if px < sx then sx = px
+ elseif px > sx + w - 2 then sx = px + w - 2 end
+ else
+ if pz >= 1 and pz <= #frames then
+ sFrame = pz
+ end
+
+ if py < sy then sy = py
+ elseif py > sy + h - 1 then sy = py + h - 1 end
+ if px < sx then sx = px
+ elseif px > sx + w - 2 then sx = px + w - 2 end
+ end
+
+ if pfx == 1 then turtlechar = ">"
+ elseif pfx == -1 then turtlechar = "<"
+ elseif pfz == 1 then turtlechar = "V"
+ elseif pfz == -1 then turtlechar = "^"
+ end
+ end
+
+ --Picture next
+ local topLayer, botLayer
+ if layerDisplay then
+ topLayer = sFrame
+ botLayer = 1
+ else
+ topLayer,botLayer = sFrame,sFrame
+ end
+
+ --How far the canvas draws. If the interface is visible, it stops short of that.
+ local wlim,hlim = 0,0
+ if not interfaceHidden then
+ wlim = 2
+ hlim = 1
+ end
+
+ for currframe = botLayer,topLayer,1 do
+ for y=sy+1,sy+h-hlim do
+ if frames[currframe][y] then
+ for x=sx+1,sx+w-wlim do
+ term.setCursorPos(x-sx,y-sy)
+ if frames[currframe][y][x] then
+ term.setBackgroundColour(frames[currframe][y][x])
+ if textEnabled and frames[currframe].textcol[y][x] and frames[currframe].text[y][x] then
+ term.setTextColour(frames[currframe].textcol[y][x])
+ term.write(frames[currframe].text[y][x])
+ else
+ term.write(" ")
+ end
+ else
+ tileExists = false
+ for i=currframe-1,botLayer,-1 do
+ if frames[i][y][x] then
+ tileExists = true
+ break
+ end
+ end
+
+ if not tileExists then
+ if blueprint then
+ term.setBackgroundColour(colours.blue)
+ term.setTextColour(colours.white)
+ if x == sx+1 and y % 4 == 1 then
+ term.write(""..((y/4) % 10))
+ elseif y == sy + 1 and x % 4 == 1 then
+ term.write(""..((x/4) % 10))
+ elseif x % 2 == 1 and y % 2 == 1 then
+ term.write("+")
+ elseif x % 2 == 1 then
+ term.write("|")
+ elseif y % 2 == 1 then
+ term.write("-")
+ else
+ term.write(" ")
+ end
+ else
+ term.setBackgroundColour(alphaC)
+ if textEnabled and frames[currframe].textcol[y][x] and frames[currframe].text[y][x] then
+ term.setTextColour(frames[currframe].textcol[y][x])
+ term.write(frames[currframe].text[y][x])
+ else
+ term.write(" ")
+ end
+ end
+ end
+ end
+ end
+ else
+ for x=sx+1,sx+w-wlim do
+ term.setCursorPos(x-sx,y-sy)
+
+ tileExists = false
+ for i=currframe-1,botLayer,-1 do
+ if frames[i][y] and frames[i][y][x] then
+ tileExists = true
+ break
+ end
+ end
+
+ if not tileExists then
+ if blueprint then
+ term.setBackgroundColour(colours.blue)
+ term.setTextColour(colours.white)
+ if x == sx+1 and y % 4 == 1 then
+ term.write(""..((y/4) % 10))
+ elseif y == sy + 1 and x % 4 == 1 then
+ term.write(""..((x/4) % 10))
+ elseif x % 2 == 1 and y % 2 == 1 then
+ term.write("+")
+ elseif x % 2 == 1 then
+ term.write("|")
+ elseif y % 2 == 1 then
+ term.write("-")
+ else
+ term.write(" ")
+ end
+ else
+ term.setBackgroundColour(alphaC)
+ term.write(" ")
+ end
+ end
+ end
+ end
+ end
+ end
+
+ --Then the printer, if he's on
+ if state == "active print" then
+ local bgColour = alphaC
+ if layering == "up" then
+ term.setCursorPos(px-sx,pz-sy)
+ if frames[sFrame] and frames[sFrame][pz-sy] and frames[sFrame][pz-sy][px-sx] then
+ bgColour = frames[sFrame][pz-sy][px-sx]
+ elseif blueprint then bgColour = colours.blue end
+ else
+ term.setCursorPos(px-sx,py-sy)
+ if frames[sFrame] and frames[sFrame][py-sy] and frames[sFrame][py-sy][px-sx] then
+ bgColour = frames[sFrame][py-sy][px-sx]
+ elseif blueprint then bgColour = colours.blue end
+ end
+
+ term.setBackgroundColour(bgColour)
+ if bgColour == colours.black then term.setTextColour(colours.white)
+ else term.setTextColour(colours.black) end
+
+ term.write(turtlechar)
+ end
+
+ --Then the buffer
+ if selectrect then
+ if buffer and rectblink == 1 then
+ for y=selectrect.y1, math.min(selectrect.y2, selectrect.y1 + buffer.height-1) do
+ for x=selectrect.x1, math.min(selectrect.x2, selectrect.x1 + buffer.width-1) do
+ if buffer.contents[y-selectrect.y1+1][x-selectrect.x1+1] then
+ term.setCursorPos(x+sx,y+sy)
+ term.setBackgroundColour(buffer.contents[y-selectrect.y1+1][x-selectrect.x1+1])
+ term.write(" ")
+ end
+ end
+ end
+ end
+
+ --This draws the "selection" box
+ local add = nil
+ if buffer then
+ term.setBackgroundColour(colours.lightGrey)
+ else
+ term.setBackgroundColour(colours.grey)
+ end
+ for i=selectrect.x1, selectrect.x2 do
+ add = (i + selectrect.y1 + rectblink) % 2 == 0
+ term.setCursorPos(i-sx,selectrect.y1-sy)
+ if add then term.write(" ") end
+ add = (i + selectrect.y2 + rectblink) % 2 == 0
+ term.setCursorPos(i-sx,selectrect.y2-sy)
+ if add then term.write(" ") end
+ end
+ for i=selectrect.y1 + 1, selectrect.y2 - 1 do
+ add = (i + selectrect.x1 + rectblink) % 2 == 0
+ term.setCursorPos(selectrect.x1-sx,i-sy)
+ if add then term.write(" ") end
+ add = (i + selectrect.x2 + rectblink) % 2 == 0
+ term.setCursorPos(selectrect.x2-sx,i-sy)
+ if add then term.write(" ") end
+ end
+ end
+end
+
+--[[Draws the colour picker on the right side of the screen, the colour pallette and the footer with any
+ messages currently being displayed
+ Params: none
+ Returns:nil
+]]--
+local function drawInterface()
+ --Picker
+ local coffset,ioffset = 0,0
+ local maxcsize = h-2
+ if h < #column + 2 then
+ maxcsize = h-4
+ coffset = columnoffset
+ ioffset = 1
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ term.setCursorPos(w-1,1)
+ term.write("^^")
+ term.setCursorPos(w-1,h-2)
+ term.write("VV")
+ end
+ for i=1,math.min(#column+1,maxcsize) do
+ term.setCursorPos(w-1, i + ioffset)
+ local ci = i+coffset
+ if ci == #column+1 then
+ term.setBackgroundColour(colours.black)
+ term.setTextColour(colours.red)
+ term.write("XX")
+ elseif state == "print" then
+ term.setBackgroundColour(column[ci])
+ if column[ci] == colours.black then
+ term.setTextColour(colours.white)
+ else term.setTextColour(colours.black) end
+
+ if requirementsDisplayed then
+ if requiredMaterials[i] < 10 then term.write(" ") end
+ term.setCursorPos(w-#tostring(requiredMaterials[i])+1, i)
+ term.write(requiredMaterials[i])
+ else
+ if i+coffset < 10 then term.write(" ") end
+ term.write(i+coffset)
+ end
+ else
+ term.setBackgroundColour(column[ci])
+ term.write(" ")
+ end
+ end
+ --Filling the whitespace with... 'greyspace' *shudder*
+ if h > #column+3 then
+ term.setTextColour(colours.grey)
+ term.setBackgroundColour(colours.lightGrey)
+ for y=#column+2,h-2 do
+ term.setCursorPos(w-1,y)
+ term.write("| ")
+ end
+ end
+ --Pallette
+ term.setCursorPos(w-1,h-1)
+ if not lSel then
+ term.setBackgroundColour(colours.black)
+ term.setTextColour(colours.red)
+ term.write("X")
+ else
+ term.setBackgroundColour(lSel)
+ term.setTextColour(lSel)
+ term.write(" ")
+ end
+ if not rSel then
+ term.setBackgroundColour(colours.black)
+ term.setTextColour(colours.red)
+ term.write("X")
+ else
+ term.setBackgroundColour(rSel)
+ term.setTextColour(rSel)
+ term.write(" ")
+ end
+ --Footer
+ if inMenu then return end
+
+ term.setCursorPos(1, h)
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ term.clearLine()
+ if mainAvailable then
+ if inDropDown then
+ term.write(string.rep(" ", #ddModes.name + 2))
+ else
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ term.write(ddModes.name.." ")
+ end
+ end
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ term.write(getStateMessage())
+
+ local coords="X:"..sx.." Y:"..sy
+ if animated then coords = coords.." Frame:"..sFrame.."/"..framecount.." " end
+ term.setCursorPos(w-#coords+1,h)
+ if state == "play" then term.setBackgroundColour(colours.lime)
+ elseif record then term.setBackgroundColour(colours.red) end
+ term.write(coords)
+
+ if animated then
+ term.setCursorPos(w-1,h)
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ term.write("<>")
+ end
+end
+
+--[[Runs an interface where users can select topics of help. Will return once the user quits the help screen.
+ Params: none
+ Returns:nil
+]]--
+local function drawHelpScreen()
+ local selectedHelp = nil
+ while true do
+ term.setBackgroundColour(colours.lightGrey)
+ term.clear()
+ if not selectedHelp then
+ term.setCursorPos(4, 1)
+ term.setTextColour(colours.brown)
+ term.write("Available modes (click for info):")
+ for i=1,#helpTopics do
+ term.setCursorPos(2, 2 + i)
+ term.setTextColour(colours.black)
+ term.write(helpTopics[i].name)
+ if helpTopics[i].key then
+ term.setTextColour(colours.red)
+ term.write(" ("..helpTopics[i].key..")")
+ end
+ end
+ term.setCursorPos(4,h)
+ term.setTextColour(colours.black)
+ term.write("Press any key to exit")
+ else
+ term.setCursorPos(4,1)
+ term.setTextColour(colours.brown)
+ term.write(helpTopics[selectedHelp].name)
+ if helpTopics[selectedHelp].key then
+ term.setTextColour(colours.red)
+ term.write(" ("..helpTopics[selectedHelp].key..")")
+ end
+ term.setCursorPos(1,3)
+ term.setTextColour(colours.black)
+ print(helpTopics[selectedHelp].message.."\n")
+ for i=1,#helpTopics[selectedHelp].controls do
+ term.setTextColour(colours.brown)
+ term.write(helpTopics[selectedHelp].controls[i][1].." ")
+ term.setTextColour(colours.black)
+ print(helpTopics[selectedHelp].controls[i][2])
+ end
+ end
+
+ local id,p1,p2,p3 = os.pullEvent()
+
+ if id == "timer" then updateTimer(p1)
+ elseif id == "key" then
+ if selectedHelp then selectedHelp = nil
+ else break end
+ elseif id == "mouse_click" then
+ if not selectedHelp then
+ if p3 >=3 and p3 <= 2+#helpTopics then
+ selectedHelp = p3-2
+ else break end
+ else
+ selectedHelp = nil
+ end
+ end
+ end
+end
+
+--[[Draws a message in the footer bar. A helper for DrawInterface, but can be called for custom messages, if the
+ inMenu paramter is set to true while this is being done (remember to set it back when done!)
+ Params: message:string = The message to be drawn
+ Returns:nil
+]]--
+local function drawMessage(message)
+ term.setCursorPos(1,h)
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ term.clearLine()
+ term.write(message)
+end
+
+--[[
+ Section: Generic Interfaces
+]]--
+
+
+--[[One of my generic text printing methods, printing a message at a specified position with width and offset.
+ No colour materials included.
+ Params: msg:string = The message to print off-center
+ height:number = The starting height of the message
+ width:number = The limit as to how many characters long each line may be
+ offset:number = The starting width offset of the message
+ Returns:number the number of lines used in printing the message
+]]--
+local function wprintOffCenter(msg, height, width, offset)
+ local inc = 0
+ local ops = 1
+ while #msg - ops > width do
+ local nextspace = 0
+ while string.find(msg, " ", ops + nextspace) and
+ string.find(msg, " ", ops + nextspace) - ops < width do
+ nextspace = string.find(msg, " ", nextspace + ops) + 1 - ops
+ end
+ local ox,oy = term.getCursorPos()
+ term.setCursorPos(width/2 - (nextspace)/2 + offset, height + inc)
+ inc = inc + 1
+ term.write(string.sub(msg, ops, nextspace + ops - 1))
+ ops = ops + nextspace
+ end
+ term.setCursorPos(width/2 - #string.sub(msg, ops)/2 + offset, height + inc)
+ term.write(string.sub(msg, ops))
+
+ return inc + 1
+end
+
+--[[Draws a message that must be clicked on or a key struck to be cleared. No options, so used for displaying
+ generic information.
+ Params: ctitle:string = The title of the confirm dialogue
+ msg:string = The message displayed in the dialogue
+ Returns:nil
+]]--
+local function displayConfirmDialogue(ctitle, msg)
+ local dialogoffset = 8
+ --We actually print twice- once to get the lines, second time to print proper. Easier this way.
+ local lines = wprintOffCenter(msg, 5, w - (dialogoffset+2) * 2, dialogoffset + 2)
+
+ term.setCursorPos(dialogoffset, 3)
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ term.write(string.rep(" ", w - dialogoffset * 2))
+ term.setCursorPos(dialogoffset + (w - dialogoffset * 2)/2 - #ctitle/2, 3)
+ term.write(ctitle)
+ term.setTextColour(colours.grey)
+ term.setBackgroundColour(colours.lightGrey)
+ term.setCursorPos(dialogoffset, 4)
+ term.write(string.rep(" ", w - dialogoffset * 2))
+ for i=5,5+lines do
+ term.setCursorPos(dialogoffset, i)
+ term.write(" "..string.rep(" ", w - (dialogoffset) * 2 - 2).." ")
+ end
+ wprintOffCenter(msg, 5, w - (dialogoffset+2) * 2, dialogoffset + 2)
+
+ --In the event of a message, the player hits anything to continue
+ while true do
+ local id,key = os.pullEvent()
+ if id == "timer" then updateTimer(key);
+ elseif id == "key" or id == "mouse_click" or id == "mouse_drag" then break end
+ end
+end
+
+--[[Produces a nice dropdown menu based on a table of strings. Depending on the position, this will auto-adjust the position
+ of the menu drawn, and allows nesting of menus and sub menus. Clicking anywhere outside the menu will cancel and return nothing
+ Params: x:int = the x position the menu should be displayed at
+ y:int = the y position the menu should be displayed at
+ options:table = the list of options available to the user, as strings or submenus (tables of strings, with a name parameter)
+ Returns:string the selected menu option.
+]]--
+local function displayDropDown(x, y, options)
+ inDropDown = true
+ --Figures out the dimensions of our thing
+ local longestX = #options.name
+ for i=1,#options do
+ local currVal = options[i]
+ if type(currVal) == "table" then currVal = currVal.name end
+
+ longestX = math.max(longestX, #currVal)
+ end
+ local xOffset = math.max(0, longestX - ((w-2) - x) + 1)
+ local yOffset = math.max(0, #options - ((h-1) - y))
+
+ local clickTimes = 0
+ local tid = nil
+ local selection = nil
+ while clickTimes < 2 do
+ drawCanvas()
+ drawInterface()
+
+ term.setCursorPos(x-xOffset,y-yOffset)
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ term.write(options.name..string.rep(" ", longestX-#options.name + 2))
+
+ for i=1,#options do
+ term.setCursorPos(x-xOffset, y-yOffset+i)
+ if i==selection and clickTimes % 2 == 0 then
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ else
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ end
+ local currVal = options[i]
+ if type(currVal) == "table" then
+ term.write(currVal.name..string.rep(" ", longestX-#currVal.name + 1))
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ term.write(">")
+ else
+ term.write(currVal..string.rep(" ", longestX-#currVal + 2))
+ end
+ end
+
+ local id, p1, p2, p3 = os.pullEvent()
+ if id == "timer" then
+ if p1 == tid then
+ clickTimes = clickTimes + 1
+ if clickTimes > 2 then
+ break
+ else
+ tid = os.startTimer(0.1)
+ end
+ else
+ updateTimer(p1)
+ drawCanvas()
+ drawInterface()
+ end
+ elseif id == "mouse_click" then
+ if p2 >=x-xOffset and p2 <= x-xOffset + longestX + 1 and p3 >= y-yOffset+1 and p3 <= y-yOffset+#options then
+ selection = p3-(y-yOffset)
+ tid = os.startTimer(0.1)
+ else
+ selection = ""
+ break
+ end
+ end
+ end
+
+ if type(selection) == "number" then
+ selection = options[selection]
+ end
+
+ if type(selection) == "string" then
+ inDropDown = false
+ return selection
+ elseif type(selection) == "table" then
+ return displayDropDown(x, y, selection)
+ end
+end
+
+--[[A custom io.read() function with a few differences- it limits the number of characters being printed,
+ waits a 1/100th of a second so any keys still in the event library are removed before input is read and
+ the timer for the selectionrectangle is continuously updated during the process.
+ Params: lim:int = the number of characters input is allowed
+ Returns:string the inputted string, trimmed of leading and tailing whitespace
+]]--
+local function readInput(lim)
+ term.setCursorBlink(true)
+
+ local inputString = ""
+ if not lim or type(lim) ~= "number" or lim < 1 then lim = w - ox end
+ local ox,oy = term.getCursorPos()
+ --We only get input from the footer, so this is safe. Change if recycling
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ term.write(string.rep(" ", lim))
+ term.setCursorPos(ox, oy)
+ --As events queue immediately, we may get an unwanted key... this will solve that problem
+ local inputTimer = os.startTimer(0.01)
+ local keysAllowed = false
+
+ while true do
+ local id,key = os.pullEvent()
+
+ if keysAllowed then
+ if id == "key" and key == 14 and #inputString > 0 then
+ inputString = string.sub(inputString, 1, #inputString-1)
+ term.setCursorPos(ox + #inputString,oy)
+ term.write(" ")
+ elseif id == "key" and key == 28 and inputString ~= string.rep(" ", #inputString) then
+ break
+ elseif id == "key" and key == keys.leftCtrl then
+ return ""
+ elseif id == "char" and #inputString < lim then
+ inputString = inputString..key
+ end
+ end
+
+ if id == "timer" then
+ if key == inputTimer then
+ keysAllowed = true
+ else
+ updateTimer(key)
+ drawCanvas()
+ drawInterface()
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ end
+ end
+ term.setCursorPos(ox,oy)
+ term.write(inputString)
+ term.setCursorPos(ox + #inputString, oy)
+ end
+
+ while string.sub(inputString, 1, 1) == " " do
+ inputString = string.sub(inputString, 2, #inputString)
+ end
+ while string.sub(inputString, #inputString, #inputString) == " " do
+ inputString = string.sub(inputString, 1, #inputString-1)
+ end
+ term.setCursorBlink(false)
+
+ return inputString
+end
+
+--[[
+ Section: Image tools
+]]--
+
+
+--[[Copies all pixels beneath the selection rectangle into the image buffer. Empty buffers are converted to nil.
+ Params: removeImage:bool = true if the image is to be erased after copying, false otherwise
+ Returns:nil
+]]--
+local function copyToBuffer(removeImage)
+ buffer = { width = selectrect.x2 - selectrect.x1 + 1, height = selectrect.y2 - selectrect.y1 + 1, contents = { } }
+
+ local containsSomething = false
+ for y=1,buffer.height do
+ buffer.contents[y] = { }
+ local f,l = sFrame,sFrame
+ if record then f,l = 1, framecount end
+
+ for fra = f,l do
+ if frames[fra][selectrect.y1 + y - 1] then
+ for x=1,buffer.width do
+ buffer.contents[y][x] = frames[sFrame][selectrect.y1 + y - 1][selectrect.x1 + x - 1]
+ if removeImage then frames[fra][selectrect.y1 + y - 1][selectrect.x1 + x - 1] = nil end
+ if buffer.contents[y][x] then containsSomething = true end
+ end
+ end
+ end
+ end
+ --I don't classify an empty buffer as a real buffer- confusing to the user.
+ if not containsSomething then buffer = nil end
+end
+
+--[[Replaces all pixels under the selection rectangle with the image buffer (or what can be seen of it). Record-dependent.
+ Params: removeBuffer:bool = true if the buffer is to be emptied after copying, false otherwise
+ Returns:nil
+]]--
+local function copyFromBuffer(removeBuffer)
+ if not buffer then return end
+
+ for y = 1, math.min(buffer.height,selectrect.y2-selectrect.y1+1) do
+ local f,l = sFrame, sFrame
+ if record then f,l = 1, framecount end
+
+ for fra = f,l do
+ if not frames[fra][selectrect.y1+y-1] then frames[fra][selectrect.y1+y-1] = { } end
+ for x = 1, math.min(buffer.width,selectrect.x2-selectrect.x1+1) do
+ frames[fra][selectrect.y1+y-1][selectrect.x1+x-1] = buffer.contents[y][x]
+ end
+ end
+ end
+
+ if removeBuffer then buffer = nil end
+end
+
+--[[Moves the entire image (or entire animation) to the specified coordinates. Record-dependent.
+ Params: newx:int = the X coordinate to move the image to
+ newy:int = the Y coordinate to move the image to
+ Returns:nil
+]]--
+local function moveImage(newx,newy)
+ if not leflim or not toplim then return end
+ if newx <=0 or newy <=0 then return end
+ local f,l = sFrame,sFrame
+ if record then f,l = 1,framecount end
+
+ for i=f,l do
+ local newlines = { }
+ for y=toplim,botlim do
+ local line = frames[i][y]
+ if line then
+ newlines[y-toplim+newy] = { }
+ for x,char in pairs(line) do
+ newlines[y-toplim+newy][x-leflim+newx] = char
+ end
+ end
+ end
+ --Exceptions that allow us to move the text as well
+ if textEnabled then
+ newlines.text = { }
+ for y=toplim,botlim do
+ local line = frames[i].text[y]
+ if line then
+ newlines.text[y-toplim+newy] = { }
+ for x,char in pairs(line) do
+ newlines.text[y-toplim+newy][x-leflim+newx] = char
+ end
+ end
+ end
+
+ newlines.textcol = { }
+ for y=toplim,botlim do
+ local line = frames[i].textcol[y]
+ if line then
+ newlines.textcol[y-toplim+newy] = { }
+ for x,char in pairs(line) do
+ newlines.textcol[y-toplim+newy][x-leflim+newx] = char
+ end
+ end
+ end
+ end
+
+ frames[i] = newlines
+ end
+end
+
+--[[Prompts the user to clear the current frame or all frames. Record-dependent.,
+ Params: none
+ Returns:nil
+]]--
+local function clearImage()
+ inMenu = true
+ if not animated then
+ drawMessage("Clear image? Y/N: ")
+ elseif record then
+ drawMessage("Clear ALL frames? Y/N: ")
+ else
+ drawMessage("Clear current frame? Y/N :")
+ end
+ if string.find(string.upper(readInput(1)), "Y") then
+ local f,l = sFrame,sFrame
+ if record then f,l = 1,framecount end
+
+ for i=f,l do
+ frames[i] = { }
+ end
+ end
+ inMenu = false
+end
+
+--[[A recursively called method (watch out for big calls!) in which every pixel of a set colour is
+ changed to another colour. Does not work on the nil colour, for obvious reasons.
+ Params: x:int = The X coordinate of the colour to flood-fill
+ y:int = The Y coordinate of the colour to flood-fill
+ targetColour:colour = the colour that is being flood-filled
+ newColour:colour = the colour with which to replace the target colour
+ Returns:nil
+]]--
+local function floodFill(x, y, targetColour, newColour)
+ if not newColour or not targetColour then return end
+ local nodeList = { }
+
+ table.insert(nodeList, {x = x, y = y})
+
+ while #nodeList > 0 do
+ local node = nodeList[1]
+ if frames[sFrame][node.y] and frames[sFrame][node.y][node.x] == targetColour then
+ frames[sFrame][node.y][node.x] = newColour
+ table.insert(nodeList, { x = node.x + 1, y = node.y})
+ table.insert(nodeList, { x = node.x, y = node.y + 1})
+ if x > 1 then table.insert(nodeList, { x = node.x - 1, y = node.y}) end
+ if y > 1 then table.insert(nodeList, { x = node.x, y = node.y - 1}) end
+ end
+ table.remove(nodeList, 1)
+ end
+end
+
+--[[
+ Section: Animation Tools
+]]--
+
+--[[Enters play mode, allowing the animation to play through. Interface is restricted to allow this,
+ and method only leaves once the player leaves play mode.
+ Params: none
+ Returns:nil
+]]--
+local function playAnimation()
+ state = "play"
+ selectedrect = nil
+
+ local animt = os.startTimer(animtime)
+ repeat
+ drawCanvas()
+ drawInterface()
+
+ local id,key,_,y = os.pullEvent()
+
+ if id=="timer" then
+ if key == animt then
+ animt = os.startTimer(animtime)
+ sFrame = (sFrame % framecount) + 1
+ else
+ updateTimer(key)
+ end
+ elseif id=="key" then
+ if key == keys.comma and animtime > 0.1 then animtime = animtime - 0.05
+ elseif key == keys.period and animtime < 0.5 then animtime = animtime + 0.05
+ elseif key == keys.space then state = "paint" end
+ elseif id=="mouse_click" and y == h then
+ state = "paint"
+ end
+ until state ~= "play"
+ os.startTimer(0.5)
+end
+
+--[[Changes the selected frame (sFrame) to the chosen frame. If this frame is above the framecount,
+ additional frames are created with a copy of the image on the selected frame.
+ Params: newframe:int = the new frame to move to
+ Returns:nil
+]]--
+local function changeFrame(newframe)
+ inMenu = true
+ if not tonumber(newframe) then
+ term.setCursorPos(1,h)
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ term.clearLine()
+
+ term.write("Go to frame: ")
+ newframe = tonumber(readInput(2))
+ if not newframe or newframe <= 0 then
+ inMenu = false
+ return
+ end
+ elseif newframe <= 0 then return end
+
+ if newframe > framecount then
+ for i=framecount+1,newframe do
+ frames[i] = {}
+ for y,line in pairs(frames[sFrame]) do
+ frames[i][y] = { }
+ for x,v in pairs(line) do
+ frames[i][y][x] = v
+ end
+ end
+ end
+ framecount = newframe
+ end
+ sFrame = newframe
+ inMenu = false
+end
+
+--[[Removes every frame leading after the frame passed in
+ Params: frame:int the non-inclusive lower bounds of the delete
+ Returns:nil
+]]--
+local function removeFramesAfter(frame)
+ inMenu = true
+ if frame==framecount then return end
+ drawMessage("Remove frames "..(frame+1).."/"..framecount.."? Y/N :")
+ local answer = string.upper(readInput(1))
+
+ if string.find(answer, string.upper("Y")) ~= 1 then
+ inMenu = false
+ return
+ end
+
+ for i=frame+1, framecount do
+ frames[i] = nil
+ end
+ framecount = frame
+ inMenu = false
+end
+
+--[[
+ Section: Printing Tools
+]]--
+
+--[[Constructs a new facing to the left of the current facing
+ Params: curx:number = The facing on the X axis
+ curz:number = The facing on the Z axis
+ hand:string = The hand of the axis ("right" or "left")
+ Returns:number,number = the new facing on the X and Z axis after a left turn
+]]--
+local function getLeft(curx, curz)
+ local hand = "left"
+ if layering == "up" then hand = "right" end
+
+ if hand == "right" then
+ if curx == 1 then return 0,-1 end
+ if curx == -1 then return 0,1 end
+ if curz == 1 then return 1,0 end
+ if curz == -1 then return -1,0 end
+ else
+ if curx == 1 then return 0,1 end
+ if curx == -1 then return 0,-1 end
+ if curz == 1 then return -1,0 end
+ if curz == -1 then return 1,0 end
+ end
+end
+
+--[[Constructs a new facing to the right of the current facing
+ Params: curx:number = The facing on the X axis
+ curz:number = The facing on the Z axis
+ hand:string = The hand of the axis ("right" or "left")
+ Returns:number,number = the new facing on the X and Z axis after a right turn
+]]--
+local function getRight(curx, curz)
+ local hand = "left"
+ if layering == "up" then hand = "right" end
+
+ if hand == "right" then
+ if curx == 1 then return 0,1 end
+ if curx == -1 then return 0,-1 end
+ if curz == 1 then return -1,0 end
+ if curz == -1 then return 1,0 end
+ else
+ if curx == 1 then return 0,-1 end
+ if curx == -1 then return 0,1 end
+ if curz == 1 then return 1,0 end
+ if curz == -1 then return -1,0 end
+ end
+end
+
+
+--[[Sends out a rednet signal requesting local printers, and will listen for any responses. Printers found are added to the
+ printerList (for ID's) and printerNames (for names)
+ Params: nil
+ Returns:nil
+]]--
+local function locatePrinters()
+ printerList = { }
+ printerNames = { name = "Printers" }
+ local oldState = state
+ state = "Locating printers, please wait... "
+ drawCanvas()
+ drawInterface()
+ state = oldState
+
+ local modemOpened = false
+ for k,v in pairs(rs.getSides()) do
+ if peripheral.isPresent(v) and peripheral.getType(v) == "modem" then
+ rednet.open(v)
+ modemOpened = true
+ break
+ end
+ end
+
+ if not modemOpened then
+ displayConfirmDialogue("Modem not found!", "No modem peripheral. Must have network modem to locate printers.")
+ return false
+ end
+
+ rednet.broadcast("$3DPRINT IDENTIFY")
+
+ while true do
+ local id, msg = rsTimeReceive(1)
+
+ if not id then break end
+ if string.find(msg, "$3DPRINT IDACK") == 1 then
+ msg = string.gsub(msg, "$3DPRINT IDACK ", "")
+ table.insert(printerList, id)
+ table.insert(printerNames, msg)
+ end
+ end
+
+ if #printerList == 0 then
+ displayConfirmDialogue("Printers not found!", "No active printers found in proximity of this computer.")
+ return false
+ else
+ return true
+ end
+end
+
+--[[Sends a request to the printer. Waits on a response and updates the state of the application accordingly.
+ Params: command:string the command to send
+ param:string a parameter to send, if any
+ Returns:nil
+]]--
+local function sendPC(command,param)
+ local msg = "$PC "..command
+ if param then msg = msg.." "..param end
+ rednet.send(printerList[selectedPrinter], msg)
+
+ while true do
+ local id,key = rsTimeReceive()
+ if id == printerList[selectedPrinter] then
+ if key == "$3DPRINT ACK" then
+ break
+ elseif key == "$3DPRINT DEP" then
+ displayConfirmDialogue("Printer Empty", "The printer has exhasted a material. Please refill slot "..param..
+ ", and click this message when ready to continue.")
+ rednet.send(printerList[selectedPrinter], msg)
+ elseif key == "$3DPRINT OOF" then
+ displayConfirmDialogue("Printer Out of Fuel", "The printer has no fuel. Please replace the material "..
+ "in slot 1 with a fuel source, then click this message.")
+ rednet.send(printerList[selectedPrinter], "$PC SS 1")
+ id,key = rsTimeReceive()
+ rednet.send(printerList[selectedPrinter], "$PC RF")
+ id,key = rsTimeReceive()
+ rednet.send(printerList[selectedPrinter], msg)
+ end
+ end
+ end
+
+ --Changes to position are handled after the event has been successfully completed
+ if command == "FW" then
+ px = px + pfx
+ pz = pz + pfz
+ elseif command == "BK" then
+ px = px - pfx
+ pz = pz - pfz
+ elseif command == "UP" then
+ if layering == "up" then
+ py = py + 1
+ else
+ py = py - 1
+ end
+ elseif command == "DW" then
+ if layering == "up" then
+ py = py - 1
+ else
+ py = py + 1
+ end
+ elseif command == "TL" then
+ pfx,pfz = getLeft(pfx,pfz)
+ elseif command == "TR" then
+ pfx,pfz = getRight(pfx,pfz)
+ elseif command == "TU" then
+ pfx = -pfx
+ pfz = -pfz
+ end
+
+ drawCanvas()
+ drawInterface()
+end
+
+--[[A printing function that commands the printer to turn to face the desired direction, if it is not already doing so
+ Params: desx:number = the normalized x direction to face
+ desz:number = the normalized z direction to face
+ Returns:nil
+]]--
+local function turnToFace(desx,desz)
+ if desx ~= 0 then
+ if pfx ~= desx then
+ local temppfx,_ = getLeft(pfx,pfz)
+ if temppfx == desx then
+ sendPC("TL")
+ elseif temppfx == -desx then
+ sendPC("TR")
+ else
+ sendPC("TU")
+ end
+ end
+ else
+ print("on the z axis")
+ if pfz ~= desz then
+ local _,temppfz = getLeft(pfx,pfz)
+ if temppfz == desz then
+ sendPC("TL")
+ elseif temppfz == -desz then
+ sendPC("TR")
+ else
+ sendPC("TU")
+ end
+ end
+ end
+end
+
+--[[Performs the print
+ Params: nil
+ Returns:nil
+]]--
+local function performPrint()
+ state = "active print"
+ if layering == "up" then
+ --An up layering starts our builder bot on the bottom left corner of our build
+ px,py,pz = leflim, 0, botlim + 1
+ pfx,pfz = 0,-1
+
+ --We move him forward and up a bit from his original position.
+ sendPC("FW")
+ sendPC("UP")
+ --For each layer that needs to be completed, we go up by one each time
+ for layers=1,#frames do
+ --We first decide if we're going forwards or back, depending on what side we're on
+ local rowbot,rowtop,rowinc = nil,nil,nil
+ if pz == botlim then
+ rowbot,rowtop,rowinc = botlim,toplim,-1
+ else
+ rowbot,rowtop,rowinc = toplim,botlim,1
+ end
+
+ for rows = rowbot,rowtop,rowinc do
+ --Then we decide if we're going left or right, depending on what side we're on
+ local linebot,linetop,lineinc = nil,nil,nil
+ if px == leflim then
+ --Facing from the left side has to be easterly- it's changed here
+ turnToFace(1,0)
+ linebot,linetop,lineinc = leflim,riglim,1
+ else
+ --Facing from the right side has to be westerly- it's changed here
+ turnToFace(-1,0)
+ linebot,linetop,lineinc = riglim,leflim,-1
+ end
+
+ for lines = linebot,linetop,lineinc do
+ --We move our turtle forward, placing the right material at each step
+ local material = frames[py][pz][px]
+ if material then
+ material = math.log10(frames[py][pz][px])/math.log10(2) + 1
+ sendPC("SS", material)
+ sendPC("PD")
+ end
+ if lines ~= linetop then
+ sendPC("FW")
+ end
+ end
+
+ --The printer then has to do a U-turn, depending on which way he's facing and
+ --which way he needs to go
+ local temppfx,temppfz = getLeft(pfx,pfz)
+ if temppfz == rowinc and rows ~= rowtop then
+ sendPC("TL")
+ sendPC("FW")
+ sendPC("TL")
+ elseif temppfz == -rowinc and rows ~= rowtop then
+ sendPC("TR")
+ sendPC("FW")
+ sendPC("TR")
+ end
+ end
+ --Now at the end of a run he does a 180 and moves up to begin the next part of the print
+ sendPC("TU")
+ if layers ~= #frames then
+ sendPC("UP")
+ end
+ end
+ --All done- now we head back to where we started.
+ if px ~= leflim then
+ turnToFace(-1,0)
+ while px ~= leflim do
+ sendPC("FW")
+ end
+ end
+ if pz ~= botlim then
+ turnToFace(0,-1)
+ while pz ~= botlim do
+ sendPC("BK")
+ end
+ end
+ turnToFace(0,-1)
+ sendPC("BK")
+ while py > 0 do
+ sendPC("DW")
+ end
+ else
+ --The front facing is at the top-left corner, facing south not north
+ px,py,pz = leflim, botlim, 1
+ pfx,pfz = 0,1
+ --We move the printer to the last layer- he prints from the back forwards
+ while pz < #frames do
+ sendPC("FW")
+ end
+
+ --For each layer in the frame we build our wall, the move back
+ for layers = 1,#frames do
+ --We first decide if we're going left or right based on our position
+ local rowbot,rowtop,rowinc = nil,nil,nil
+ if px == leflim then
+ rowbot,rowtop,rowinc = leflim,riglim,1
+ else
+ rowbot,rowtop,rowinc = riglim,leflim,-1
+ end
+
+ for rows = rowbot,rowtop,rowinc do
+ --Then we decide if we're going up or down, depending on our given altitude
+ local linebot,linetop,lineinc = nil,nil,nil
+ if py == botlim then
+ linebot,linetop,lineinc = botlim,toplim,-1
+ else
+ linebot,linetop,lineinc = toplim,botlim,1
+ end
+
+ for lines = linebot,linetop,lineinc do
+ --We move our turtle up/down, placing the right material at each step
+ local material = frames[pz][py][px]
+ if material then
+ material = math.log10(frames[pz][py][px])/math.log10(2) + 1
+ sendPC("SS", material)
+ sendPC("PF")
+ end
+ if lines ~= linetop then
+ if lineinc == 1 then sendPC("DW")
+ else sendPC("UP") end
+ end
+ end
+
+ if rows ~= rowtop then
+ turnToFace(rowinc,0)
+ sendPC("FW")
+ turnToFace(0,1)
+ end
+ end
+
+ if layers ~= #frames then
+ sendPC("TU")
+ sendPC("FW")
+ sendPC("TU")
+ end
+ end
+ --He's easy to reset
+ while px ~= leflim do
+ turnToFace(-1,0)
+ sendPC("FW")
+ end
+ turnToFace(0,1)
+ end
+
+ sendPC("DE")
+
+ displayConfirmDialogue("Print complete", "The 3D print was successful.")
+end
+
+--[[
+ Section: Interface
+]]--
+
+--[[Runs the printing interface. Allows users to find/select a printer, the style of printing to perform and to begin the operation
+ Params: none
+ Returns:boolean true if printing was started, false otherwse
+]]--
+local function runPrintInterface()
+ calculateMaterials()
+ --There's nothing on canvas yet!
+ if not botlim then
+ displayConfirmDialogue("Cannot Print Empty Canvas", "There is nothing on canvas that "..
+ "can be printed, and the operation cannot be completed.")
+ return false
+ end
+ --No printers nearby
+ if not locatePrinters() then
+ return false
+ end
+
+ layering = "up"
+ requirementsDisplayed = false
+ selectedPrinter = 1
+ while true do
+ drawCanvas()
+ term.setBackgroundColour(colours.lightGrey)
+ for i=1,10 do
+ term.setCursorPos(1,i)
+ term.clearLine()
+ end
+ drawInterface()
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.black)
+
+ local msg = "3D Printing"
+ term.setCursorPos(w/2-#msg/2 - 2, 1)
+ term.write(msg)
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ if(requirementsDisplayed) then
+ msg = "Count:"
+ else
+ msg = " Slot:"
+ end
+ term.setCursorPos(w-3-#msg, 1)
+ term.write(msg)
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.black)
+
+ term.setCursorPos(7, 2)
+ term.write("Layering")
+ drawPictureTable(layerUpIcon, 3, 3, colours.white)
+ drawPictureTable(layerForwardIcon, 12, 3, colours.white)
+ if layering == "up" then
+ term.setBackgroundColour(colours.red)
+ else
+ term.setBackgroundColour(colours.lightGrey)
+ end
+ term.setCursorPos(3, 9)
+ term.write("Upwards")
+ if layering == "forward" then
+ term.setBackgroundColour(colours.red)
+ else
+ term.setBackgroundColour(colours.lightGrey)
+ end
+ term.setCursorPos(12, 9)
+ term.write("Forward")
+
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.black)
+ term.setCursorPos(31, 2)
+ term.write("Printer ID")
+ term.setCursorPos(33, 3)
+ if #printerList > 1 then
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ else
+ term.setTextColour(colours.red)
+ end
+ term.write(" "..printerNames[selectedPrinter].." ")
+
+ term.setBackgroundColour(colours.grey)
+ term.setTextColour(colours.lightGrey)
+ term.setCursorPos(25, 10)
+ term.write(" Cancel ")
+ term.setCursorPos(40, 10)
+ term.write(" Print ")
+
+ local id, p1, p2, p3 = os.pullEvent()
+
+ if id == "timer" then
+ updateTimer(p1)
+ elseif id == "mouse_click" then
+ --Layering Buttons
+ if p2 >= 3 and p2 <= 9 and p3 >= 3 and p3 <= 9 then
+ layering = "up"
+ elseif p2 >= 12 and p2 <= 18 and p3 >= 3 and p3 <= 9 then
+ layering = "forward"
+ --Count/Slot
+ elseif p2 >= w - #msg - 3 and p2 <= w - 3 and p3 == 1 then
+ requirementsDisplayed = not requirementsDisplayed
+ --Printer ID
+ elseif p2 >= 33 and p2 <= 33 + #printerNames[selectedPrinter] and p3 == 3 and #printerList > 1 then
+ local chosenName = displayDropDown(33, 3, printerNames)
+ for i=1,#printerNames do
+ if printerNames[i] == chosenName then
+ selectedPrinter = i
+ break;
+ end
+ end
+ --Print and Cancel
+ elseif p2 >= 25 and p2 <= 32 and p3 == 10 then
+ break
+ elseif p2 >= 40 and p2 <= 46 and p3 == 10 then
+ rednet.send(printerList[selectedPrinter], "$3DPRINT ACTIVATE")
+ ready = false
+ while true do
+ local id,msg = rsTimeReceive(10)
+
+ if id == printerList[selectedPrinter] and msg == "$3DPRINT ACTACK" then
+ ready = true
+ break
+ end
+ end
+ if ready then
+ performPrint()
+ break
+ else
+ displayConfirmDialogue("Printer Didn't Respond", "The printer didn't respond to the activation command. Check to see if it's online")
+ end
+ end
+ end
+ end
+ state = "paint"
+end
+
+--[[Performs a legacy save. When the dropdown menu is unavailable, it requests the user to save
+ or exit using keyboard shortcuts rather than selecting a menu option from the dropdown.
+ Pressing the control key again will cancel the save operation.
+ Params: none
+ Returns:string = the selection made
+]]--
+local function performLegacySaveExit()
+ local saveMsg = "(S)ave/(E)xit?"
+ if w < #saveMsg then saveMsg = "S/E?" end
+
+ term.setCursorPos(1,h)
+ term.setBackgroundColour(colours.lightGrey)
+ term.setTextColour(colours.grey)
+ term.clearLine()
+ term.write(saveMsg)
+
+ while true do
+ local id,val = os.pullEvent()
+ if id == "timer" then updateTimer(val)
+ elseif id == "key" then
+ if val == keys.s then return "save"
+ elseif val == keys.e then
+ --Get rid of the extra event
+ os.pullEvent("char")
+ return "exit"
+ elseif val == keys.leftCtrl then return nil
+ end
+ end
+ end
+end
+
+--[[This function changes the current paint program to another tool or mode, depending on user input. Handles
+ any necessary changes in logic involved in that.
+ Params: mode:string = the name of the mode to change to
+ Returns:nil
+]]--
+local function performSelection(mode)
+ if not mode or mode == "" then return
+
+ elseif mode == "help" and helpAvailable then
+ drawHelpScreen()
+
+ elseif mode == "blueprint on" then
+ blueprint = true
+ for i=1,#ddModes[2] do if ddModes[2][i] == "blueprint on" then
+ ddModes[2][i] = "blueprint off"
+ end end
+
+ elseif mode == "blueprint off" then
+ blueprint = false
+ for i=1,#ddModes[2] do if ddModes[2][i] == "blueprint off" then
+ ddModes[2][i] = "blueprint on"
+ end end
+
+ elseif mode == "layers on" then
+ layerDisplay = true
+ for i=1,#ddModes[2] do if ddModes[2][i] == "layers on" then
+ ddModes[2][i] = "layers off"
+ end end
+
+ elseif mode == "layers off" then
+ layerDisplay = false
+ for i=1,#ddModes[2] do if ddModes[2][i] == "layers off" then
+ ddModes[2][i] = "layers on"
+ end end
+
+ elseif mode == "direction on" then
+ printDirection = true
+ for i=1,#ddModes[2] do if ddModes[2][i] == "direction on" then
+ ddModes[2][i] = "direction off"
+ end end
+
+ elseif mode == "direction off" then
+ printDirection = false
+ for i=1,#ddModes[2] do if ddModes[2][i] == "direction off" then
+ ddModes[2][i] = "direction on"
+ end end
+
+ elseif mode == "hide interface" then
+ interfaceHidden = true
+
+ elseif mode == "show interface" then
+ interfaceHidden = false
+
+ elseif mode == "go to" then
+ changeFrame()
+
+ elseif mode == "remove" then
+ removeFramesAfter(sFrame)
+
+ elseif mode == "play" then
+ playAnimation()
+
+ elseif mode == "copy" then
+ if selectrect and selectrect.x1 ~= selectrect.x2 then
+ copyToBuffer(false)
+ end
+
+ elseif mode == "cut" then
+ if selectrect and selectrect.x1 ~= selectrect.x2 then
+ copyToBuffer(true)
+ end
+
+ elseif mode == "paste" then
+ if selectrect and selectrect.x1 ~= selectrect.x2 then
+ copyFromBuffer(false)
+ end
+
+ elseif mode == "hide" then
+ selectrect = nil
+ if state == "select" then state = "corner select" end
+
+ elseif mode == "alpha to left" then
+ if lSel then alphaC = lSel end
+
+ elseif mode == "alpha to right" then
+ if rSel then alphaC = rSel end
+
+ elseif mode == "record" then
+ record = not record
+
+ elseif mode == "clear" then
+ if state=="select" then buffer = nil
+ else clearImage() end
+
+ elseif mode == "select" then
+ if state=="corner select" or state=="select" then
+ state = "paint"
+ elseif selectrect and selectrect.x1 ~= selectrect.x2 then
+ state = "select"
+ else
+ state = "corner select"
+ end
+
+ elseif mode == "print" then
+ state = "print"
+ runPrintInterface()
+ state = "paint"
+
+ elseif mode == "save" then
+ if animated then saveNFA(sPath)
+ elseif textEnabled then saveNFT(sPath)
+ else saveNFP(sPath) end
+
+ elseif mode == "exit" then
+ isRunning = false
+
+ elseif mode ~= state then state = mode
+ else state = "paint"
+ end
+end
+
+--[[The main function of the program, reads and handles all events and updates them accordingly. Mode changes,
+ painting to the canvas and general selections are done here.
+ Params: none
+ Returns:nil
+]]--
+local function handleEvents()
+ recttimer = os.startTimer(0.5)
+ while isRunning do
+ drawCanvas()
+ if not interfaceHidden then drawInterface() end
+
+ if state == "text" then
+ term.setCursorPos(textCurX - sx, textCurY - sy)
+ term.setCursorBlink(true)
+ end
+
+ local id,p1,p2,p3 = os.pullEvent()
+ term.setCursorBlink(false)
+ if id=="timer" then
+ updateTimer(p1)
+ elseif (id=="mouse_click" or id=="mouse_drag") and not interfaceHidden then
+ if p2 >=w-1 and p3 < #column+1 then
+ local off = 0
+ local cansel = true
+ if h < #column + 2 then
+ if p3 == 1 then
+ if columnoffset > 0 then columnoffset = columnoffset-1 end
+ cansel = false
+ elseif p3 == h-2 then
+ if columnoffset < #column-(h-4)+1 then columnoffset = columnoffset+1 end
+ cansel = false
+ else
+ off = columnoffset - 1
+ end
+ end
+ --This rather handily accounts for the nil case (p3+off=#column+1)
+ if p1==1 and cansel then lSel = column[p3+off]
+ elseif p1==2 and cansel then rSel = column[p3+off] end
+ elseif p2 >=w-1 and p3==#column+1 then
+ if p1==1 then lSel = nil
+ else rSel = nil end
+ elseif p2==w-1 and p3==h and animated then
+ changeFrame(sFrame-1)
+ elseif p2==w and p3==h and animated then
+ changeFrame(sFrame+1)
+ elseif p2 <= #ddModes.name + 2 and p3==h and mainAvailable then
+ local sel = displayDropDown(1, h-1, ddModes)
+ performSelection(sel)
+ elseif p2 < w-1 and p3 <= h-1 then
+ if state=="pippette" then
+ if p1==1 then
+ if frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then
+ lSel = frames[sFrame][p3+sy][p2+sx]
+ end
+ elseif p1==2 then
+ if frames[sFrame][p3+sy] and frames[sFrame][p3+sy][p2+sx] then
+ rSel = frames[sFrame][p3+sy][p2+sx]
+ end
+ end
+ elseif state=="move" then
+ updateImageLims(record)
+ moveImage(p2,p3)
+ elseif state=="flood" then
+ if p1 == 1 and lSel and frames[sFrame][p3+sy] then
+ floodFill(p2,p3,frames[sFrame][p3+sy][p2+sx],lSel)
+ elseif p1 == 2 and rSel and frames[sFrame][p3+sy] then
+ floodFill(p2,p3,frames[sFrame][p3+sy][p2+sx],rSel)
+ end
+ elseif state=="corner select" then
+ if not selectrect then
+ selectrect = { x1=p2+sx, x2=p2+sx, y1=p3+sy, y2=p3+sy }
+ elseif selectrect.x1 ~= p2+sx and selectrect.y1 ~= p3+sy then
+ if p2+sx<selectrect.x1 then selectrect.x1 = p2+sx
+ else selectrect.x2 = p2+sx end
+
+ if p3+sy<selectrect.y1 then selectrect.y1 = p3+sy
+ else selectrect.y2 = p3+sy end
+
+ state = "select"
+ end
+ elseif state=="textpaint" then
+ local paintCol = lSel
+ if p1 == 2 then paintCol = rSel end
+ if frames[sFrame].textcol[p3+sy] then
+ frames[sFrame].textcol[p3+sy][p2+sx] = paintCol
+ end
+ elseif state=="text" then
+ textCurX = p2 + sx
+ textCurY = p3 + sy
+ elseif state=="select" then
+ if p1 == 1 then
+ local swidth = selectrect.x2 - selectrect.x1
+ local sheight = selectrect.y2 - selectrect.y1
+
+ selectrect.x1 = p2 + sx
+ selectrect.y1 = p3 + sy
+ selectrect.x2 = p2 + swidth + sx
+ selectrect.y2 = p3 + sheight + sy
+ elseif p1 == 2 and p2 < w-2 and p3 < h-1 and boxdropAvailable then
+ inMenu = true
+ local sel = displayDropDown(p2, p3, srModes)
+ inMenu = false
+ performSelection(sel)
+ end
+ else
+ local f,l = sFrame,sFrame
+ if record then f,l = 1,framecount end
+ local bwidth = 0
+ if state == "brush" then bwidth = brushsize-1 end
+
+ for i=f,l do
+ for x = math.max(1,p2+sx-bwidth),p2+sx+bwidth do
+ for y = math.max(1,p3+sy-bwidth), p3+sy+bwidth do
+ if math.abs(x - (p2+sx)) + math.abs(y - (p3+sy)) <= bwidth then
+ if not frames[i][y] then frames[i][y] = {} end
+ if p1==1 then frames[i][y][x] = lSel
+ else frames[i][y][x] = rSel end
+
+ if textEnabled then
+ if not frames[i].text[y] then frames[i].text[y] = { } end
+ if not frames[i].textcol[y] then frames[i].textcol[y] = { } end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ elseif id=="char" then
+ if state=="text" then
+ if not frames[sFrame][textCurY] then frames[sFrame][textCurY] = { } end
+ if not frames[sFrame].text[textCurY] then frames[sFrame].text[textCurY] = { } end
+ if not frames[sFrame].textcol[textCurY] then frames[sFrame].textcol[textCurY] = { } end
+
+ if rSel then frames[sFrame][textCurY][textCurX] = rSel end
+ if lSel then
+ frames[sFrame].text[textCurY][textCurX] = p1
+ frames[sFrame].textcol[textCurY][textCurX] = lSel
+ else
+ frames[sFrame].text[textCurY][textCurX] = " "
+ frames[sFrame].textcol[textCurY][textCurX] = rSel
+ end
+
+ textCurX = textCurX+1
+ if textCurX > w + sx - 2 then sx = textCurX - w + 2 end
+ elseif tonumber(p1) then
+ if state=="brush" and tonumber(p1) > 1 then
+ brushsize = tonumber(p1)
+ elseif animated and tonumber(p1) > 0 then
+ changeFrame(tonumber(p1))
+ end
+ end
+ elseif id=="key" then
+ --All standard interface methods are locked when the interface is hidden
+ if interfaceHidden then
+ if p1==keys.grave then
+ performSelection("show interface")
+ end
+ --Text needs special handlers (all other keyboard shortcuts are of course reserved for typing)
+ elseif state=="text" then
+ if p1==keys.backspace and textCurX > 1 then
+ textCurX = textCurX-1
+ if frames[sFrame].text[textCurY] then
+ frames[sFrame].text[textCurY][textCurX] = nil
+ frames[sFrame].textcol[textCurY][textCurX] = nil
+ end
+ if textCurX < sx then sx = textCurX end
+ elseif p1==keys.left and textCurX > 1 then
+ textCurX = textCurX-1
+ if textCurX-1 < sx then sx = textCurX-1 end
+ elseif p1==keys.right then
+ textCurX = textCurX+1
+ if textCurX > w + sx - 2 then sx = textCurX - w + 2 end
+ elseif p1==keys.up and textCurY > 1 then
+ textCurY = textCurY-1
+ if textCurY-1 < sy then sy = textCurY-1 end
+ elseif p1==keys.down then
+ textCurY = textCurY+1
+ if textCurY > h + sy - 1 then sy = textCurY - h + 1 end
+ end
+
+ elseif p1==keys.leftCtrl then
+ local sel = nil
+ if mainAvailable then
+ sel = displayDropDown(1, h-1, ddModes[#ddModes])
+ else sel = performLegacySaveExit() end
+ performSelection(sel)
+ elseif p1==keys.leftAlt then
+ local sel = displayDropDown(1, h-1, ddModes[1])
+ performSelection(sel)
+ elseif p1==keys.h then
+ performSelection("help")
+ elseif p1==keys.x then
+ performSelection("cut")
+ elseif p1==keys.c then
+ performSelection("copy")
+ elseif p1==keys.v then
+ performSelection("paste")
+ elseif p1==keys.z then
+ performSelection("clear")
+ elseif p1==keys.s then
+ performSelection("select")
+ elseif p1==keys.tab then
+ performSelection("hide")
+ elseif p1==keys.q then
+ performSelection("alpha to left")
+ elseif p1==keys.w then
+ performSelection("alpha to right")
+ elseif p1==keys.f then
+ performSelection("flood")
+ elseif p1==keys.b then
+ performSelection("brush")
+ elseif p1==keys.m then
+ performSelection("move")
+ elseif p1==keys.backslash and animated then
+ performSelection("record")
+ elseif p1==keys.p then
+ performSelection("pippette")
+ elseif p1==keys.g and animated then
+ performSelection("go to")
+ elseif p1==keys.grave then
+ performSelection("hide interface")
+ elseif p1==keys.period and animated then
+ changeFrame(sFrame+1)
+ elseif p1==keys.comma and animated then
+ changeFrame(sFrame-1)
+ elseif p1==keys.r and animated then
+ performSelection("remove")
+ elseif p1==keys.space and animated then
+ performSelection("play")
+ elseif p1==keys.t and textEnabled then
+ performSelection("text")
+ sleep(0.01)
+ elseif p1==keys.y and textEnabled then
+ performSelection("textpaint")
+ elseif p1==keys.left then
+ if state == "move" and toplim then
+ updateImageLims(record)
+ if toplim and leflim then
+ moveImage(leflim-1,toplim)
+ end
+ elseif state=="select" and selectrect.x1 > 1 then
+ selectrect.x1 = selectrect.x1-1
+ selectrect.x2 = selectrect.x2-1
+ elseif sx > 0 then sx=sx-1 end
+ elseif p1==keys.right then
+ if state == "move" then
+ updateImageLims(record)
+ if toplim and leflim then
+ moveImage(leflim+1,toplim)
+ end
+ elseif state=="select" then
+ selectrect.x1 = selectrect.x1+1
+ selectrect.x2 = selectrect.x2+1
+ else sx=sx+1 end
+ elseif p1==keys.up then
+ if state == "move" then
+ updateImageLims(record)
+ if toplim and leflim then
+ moveImage(leflim,toplim-1)
+ end
+ elseif state=="select" and selectrect.y1 > 1 then
+ selectrect.y1 = selectrect.y1-1
+ selectrect.y2 = selectrect.y2-1
+ elseif sy > 0 then sy=sy-1 end
+ elseif p1==keys.down then
+ if state == "move" then
+ updateImageLims(record)
+ if toplim and leflim then
+ moveImage(leflim,toplim+1)
+ end
+ elseif state=="select" then
+ selectrect.y1 = selectrect.y1+1
+ selectrect.y2 = selectrect.y2+1
+ else sy=sy+1 end
+ end
+ end
+ end
+end
+
+--[[
+ Section: Main
+]]--
+
+--The first thing done is deciding what features we actually have, given the screen size
+if w < 7 or h < 4 then
+ --NPaintPro simply doesn't work at certain configurations
+ shell.run("clear")
+ print("Screen too small")
+ os.pullEvent("key")
+ return
+end
+--And reduces the number of features in others.
+determineAvailableServices()
+
+--There is no b&w support for NPP.
+if not term.isColour() then
+ shell.run("clear")
+ print("NPaintPro\nBy NitrogenFingers\n\nNPaintPro can only be run on advanced "..
+ "computers. Please reinstall on an advanced computer.")
+ return
+end
+
+--Taken almost directly from edit (for consistency)
+local tArgs = {...}
+
+--Command line options can appear before the file path to specify the file format
+local ca = 1
+while ca <= #tArgs do
+ if tArgs[ca] == "-a" then animated = true
+ elseif tArgs[ca] == "-t" then textEnabled = true
+ elseif tArgs[ca] == "-d" then interfaceHidden = true
+ elseif string.sub(tArgs[ca], 1, 1) == "-" then
+ print("Unrecognized option: "..tArgs[ca])
+ return
+ else break end
+ ca = ca + 1
+end
+
+--Presently, animations and text files are not supported
+if animated and textEnabled then
+ print("No support for animated text files- cannot have both -a and -t")
+ return
+end
+
+--Filepaths must be added if the screen is too small
+if #tArgs < ca then
+ if not filemakerAvailable then
+ print("Usage: npaintpro [-a,-t,-d] <path>")
+ return
+ else
+ --Otherwise do the logo draw early, to determine the file.
+ drawLogo()
+ if not runFileMaker() then return end
+ end
+else
+ if not interfaceHidden then drawLogo() end
+ sPath = shell.resolve(tArgs[ca])
+end
+
+if fs.exists(sPath) then
+ if fs.isDir(sPath) then
+ print("Cannot edit a directory.")
+ return
+ elseif string.find(sPath, ".nfp") ~= #sPath-3 and string.find(sPath, ".nfa") ~= #sPath-3 and
+ string.find(sPath, ".nft") ~= #sPath-3 then
+ print("Can only edit .nfp, .nft and .nfa files:",string.find(sPath, ".nfp"),#sPath-3)
+ return
+ end
+
+ if string.find(sPath, ".nfa") == #sPath-3 then
+ animated = true
+ end
+
+ if string.find(sPath, ".nft") == #sPath-3 then
+ textEnabled = true
+ end
+
+ if string.find(sPath, ".nfp") == #sPath-3 and animated then
+ print("Convert to nfa? Y/N")
+ if string.find(string.lower(io.read()), "y") then
+ local nsPath = string.sub(sPath, 1, #sPath-1).."a"
+ fs.move(sPath, nsPath)
+ sPath = nsPath
+ else
+ animated = false
+ end
+ end
+
+ --Again this is possible, I just haven't done it. Maybe I will?
+ if textEnabled and (string.find(sPath, ".nfp") == #sPath-3 or string.find(sPath, ".nfa") == #sPath-3) then
+ print("Cannot convert to nft")
+ end
+else
+ if not animated and not textEnabled and string.find(sPath, ".nfp") ~= #sPath-3 then
+ sPath = sPath..".nfp"
+ elseif animated and string.find(sPath, ".nfa") ~= #sPath-3 then
+ sPath = sPath..".nfa"
+ elseif textEnabled and string.find(sPath, ".nft") ~= #sPath-3 then
+ sPath = sPath..".nft"
+ end
+end
+
+init()
+handleEvents()
+
+term.setBackgroundColour(colours.black)
+shell.run("clear")
diff --git a/programs/npaintpro.shortcut b/programs/npaintpro.shortcut
new file mode 100644
index 0000000..51a827a
--- /dev/null
+++ b/programs/npaintpro.shortcut
@@ -0,0 +1,2 @@
+name: NPaintPro
+shortcut: /programs/npaintpro.lua \ No newline at end of file
diff --git a/startup b/startup
index b188287..94315d8 100644
--- a/startup
+++ b/startup
@@ -1,72 +1,2 @@
--- bits-UI: An operating system for ComputerCraft. Licensed with GPL-3.0.
-local boot = "/system/boot.lua"
-
-function bootloader()
- term.setCursorPos(1,1)
- print("Welcome to the BUBL boot loader!\n")
- term.setCursorPos(1,3)
- print("1. Boot bits-UI\n")
- term.setCursorPos(1,4)
- print("2. Update bits-UI\n")
- term.setCursorPos(1,5)
- print("3. Boot CraftOS\n")
- term.setCursorPos(1,7)
- term.write("> ")
-end
-
-function clear()
- term.clear()
- term.setCursorPos(1,1)
- term.setTextColor(colors.white)
-end
-
-function bootloaderInput()
- local input = read()
-
- if input == "1" then
- clear()
- shell.run("/system/boot.lua")
- elseif input == "2" then
- clear()
- if fs.exists("dev_mode") then
- print("You are running developer mode! You cannot update bit-UI.")
- sleep(3)
- clear()
- bootloader()
- bootloaderInput()
- else
- print("Running updater...")
- sleep(3)
- shell.run("pastebin", "run", "7XY80hfG")
- end
- elseif input == "3" then
- clear()
- print(os.version())
- term.setCursorPos(1,2)
- else
- print("[ERROR] Invalid number.")
- sleep(2)
- clear()
- bootloader()
- bootloaderInput()
- end
-
-end
-
-
-if fs.exists(boot) then
- term.setTextColor(colors.green)
- print("Boot detected!")
-else
- clear()
- term.setTextColor(colors.red)
- print("[ERROR] System has been halted.")
- term.setCursorPos(1,2)
- print("Details: Cannot find boot.lua")
- sleep(3)
- os.shutdown()
-end
-
-clear()
-bootloader()
-bootloaderInput() \ No newline at end of file
+-- It redirects to the startup lua file if it's running a older version of CraftOS.
+shell.run("startup.lua")
diff --git a/startup.lua b/startup.lua
new file mode 100644
index 0000000..82c5a06
--- /dev/null
+++ b/startup.lua
@@ -0,0 +1,88 @@
+--[[
+ bits-UI Boot Loader (BUBL): A boot loader for bits-UI
+ Copyright (C) 2019 Alee14
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+]]--
+local boot = "/system/boot.lua"
+
+function bootloader()
+ term.setCursorPos(1,1)
+ print("Welcome to the BUBL boot loader!\n")
+ term.setCursorPos(1,3)
+ print("1. Boot bits-UI\n")
+ term.setCursorPos(1,4)
+ print("2. Update bits-UI\n")
+ term.setCursorPos(1,5)
+ print("3. Boot CraftOS\n")
+ term.setCursorPos(1,7)
+ term.write("> ")
+end
+
+function clear()
+ term.clear()
+ term.setCursorPos(1,1)
+ term.setTextColor(colors.white)
+end
+
+function bootloaderInput()
+ local input = read()
+
+ if input == "1" then
+ clear()
+ shell.run("/system/boot.lua")
+ elseif input == "2" then
+ clear()
+ if fs.exists("dev_mode") then
+ print("You are running developer mode! You cannot update bit-UI.")
+ sleep(3)
+ clear()
+ bootloader()
+ bootloaderInput()
+ else
+ print("Running updater...")
+ sleep(3)
+ shell.run("pastebin", "run", "7XY80hfG")
+ end
+ elseif input == "3" then
+ clear()
+ print(os.version())
+ term.setCursorPos(1,2)
+ else
+ print("[ERROR] Invalid number.")
+ sleep(2)
+ clear()
+ bootloader()
+ bootloaderInput()
+ end
+
+end
+
+clear()
+
+if fs.exists(boot) then
+ term.setTextColor(colors.green)
+ print("Boot detected!")
+ sleep(1)
+else
+ clear()
+ term.setTextColor(colors.red)
+ print("[ERROR] System has been halted.")
+ term.setCursorPos(1,2)
+ print("Details: Cannot find boot.lua")
+ sleep(3)
+ os.shutdown()
+end
+
+clear()
+bootloader()
+bootloaderInput()
diff --git a/system/apis/flib.lua b/system/apis/flib.lua
new file mode 100644
index 0000000..c3b7150
--- /dev/null
+++ b/system/apis/flib.lua
@@ -0,0 +1,124 @@
+--[[
+ fLib by NDFJay
+]]--
+function exists(path)
+ local file = assert(io.open(path, "r"))
+ if file ~= nil then
+ file:close()
+ return true
+ end
+
+ return false
+end
+
+function getTable(path)
+ if exists(path) then
+ local file = io.open(path, "r")
+ local lines = {}
+ local i = 1
+ local line = file:read("*l")
+ while line ~= nil do
+ lines[i] = line
+ line = file:read("*l")
+ i = i + 1
+ end
+ file:close()
+ return lines
+ end
+ return {}
+end
+
+function getLine(path, n)
+ if exists(path) then
+ local lines = getTable(path)
+ return lines[n]
+ end
+ return ""
+end
+
+function getText(path)
+ if exists(path) then
+ local file = assert(io.open(path, "r"))
+ return file:read("*a")
+ end
+ return ""
+end
+
+function fappend(path, text)
+ local file = assert(io.open(path, "a"))
+ file:write(text.."\n")
+ file:close()
+end
+
+function fwrite(path, text)
+ local file = assert(io.open(path, "w"))
+ file:write(text)
+ file:close()
+end
+
+function fwriteAtStart(path, text)
+ local _text = getText(path)
+ fwrite(path, text.."\n".._text)
+end
+
+function fwriteFromTable(path, t)
+ local text = ""
+ for _, line in pairs(t) do
+ text = text..line.."\n"
+ end
+ fwrite(path, text)
+end
+
+function fappendFromTable(path, t)
+ local text = ""
+ for _, line in pairs(t) do
+ text = text..line.."\n"
+ end
+ fappend(path, text)
+end
+
+function fwriteAtStartFromTable(path, t)
+ local text = ""
+ for _, line in pairs(t) do
+ text = text..line.."\n"
+ end
+ fwriteAtStart(path, text)
+end
+
+function replaceLine(path, n, text)
+ local lines = getTable(path)
+ lines[n] = text
+ fwriteFromTable(path, lines)
+end
+
+function getName(path)
+ if exists(path) then
+ local lastSlashPos = 1
+ for i = 1, path:len() do
+ if path:sub(i, i) == "/" then
+ lastSlashPos = i
+ end
+ end
+
+ return path:sub(lastSlashPos + 1)
+ end
+ return ""
+end
+
+function getPath(path)
+ if exists(path) then
+ local lastSlashPos = 1
+ for i = 1, path:len() do
+ if path:sub(i, i) == "/" then
+ lastSlashPos = i
+ end
+ end
+
+ return path:sub(1, lastSlashPos)
+ end
+ return ""
+end
+
+function fremove(path)
+ os.remove(path)
+end \ No newline at end of file
diff --git a/system/boot.lua b/system/boot.lua
index 3061d57..c25fae2 100644
--- a/system/boot.lua
+++ b/system/boot.lua
@@ -1,5 +1,17 @@
+--[[
+ bits-UI Boot: A boot script for bits-UI.
+ Copyright (C) 2019 Alee14
--- bits-UI: An operating system for ComputerCraft. Licensed with GPL-3.0.
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+]]--
local version = "1.0 Alpha 2"
local desktop = "/system/desktop.lua"
@@ -53,6 +65,8 @@ else
term.setTextColor(colors.blue)
print("[INFO] Config has not been found!")
print("[INFO] You will be sent to the OOBE setup...")
+ sleep(3)
+ shell.run("/system/setup.lua")
end
sleep(3)
diff --git a/system/desktop.lua b/system/desktop.lua
index 83abc6f..e0afe4f 100644
--- a/system/desktop.lua
+++ b/system/desktop.lua
@@ -1,4 +1,17 @@
--- bits-UI: An operating system for ComputerCraft. Licensed with GPL-3.0.
+--[[
+ bits-UI Desktop: A Desktop for bits-UI
+ Copyright (C) 2019 Alee14
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+]]--
function titleBar()
local time = os.time()
diff --git a/system/setup.lua b/system/setup.lua
new file mode 100644
index 0000000..644d0ed
--- /dev/null
+++ b/system/setup.lua
@@ -0,0 +1,14 @@
+term.clear()
+term.setCursorPos(1,1)
+term.setTextColor(colors.white)
+
+if fs.exists("/system/skel/README.txt") then
+ shell.run("copy", "/system/skel/README.txt", "/home")
+else
+ print("[ERROR] Unable to find README.txt...")
+end
+
+print("Welcome to the setup!")
+
+sleep(3)
+shell.run("/system/desktop.lua") \ No newline at end of file
diff --git a/system/skel/README.txt b/system/skel/README.txt
new file mode 100644
index 0000000..ef149d6
--- /dev/null
+++ b/system/skel/README.txt
@@ -0,0 +1,7 @@
+Thanks for downloading bits-UI!
+
+Please note that this project is in very early development and there might not be as much features as you'll expect but in future builds will be more!
+
+Oh yeah! Feel free to contribute to this project by the following link!
+
+Source Code: https://github.com/Alee14/bits-UI \ No newline at end of file