From a18407bdb9c87ac970418cd625abe38f550ab45c Mon Sep 17 00:00:00 2001 From: Andrew Lee Date: Tue, 5 Mar 2019 19:26:59 -0500 Subject: Added a bunch of stuff --- README.md | 11 +- programs/ink.lua | 3153 ++++++++++++++++++++++++++++++++++++++++ programs/ink.shortcut | 2 + programs/luaide.shortcut | 2 + programs/mousebrowser.lua | 1298 +++++++++++++++++ programs/mousebrowser.shortcut | 2 + programs/npaintpro.lua | 2827 +++++++++++++++++++++++++++++++++++ programs/npaintpro.shortcut | 2 + startup | 74 +- startup.lua | 88 ++ system/apis/flib.lua | 124 ++ system/boot.lua | 16 +- system/desktop.lua | 15 +- system/setup.lua | 14 + system/skel/README.txt | 7 + 15 files changed, 7560 insertions(+), 75 deletions(-) create mode 100644 programs/ink.lua create mode 100644 programs/ink.shortcut create mode 100644 programs/luaide.shortcut create mode 100644 programs/mousebrowser.lua create mode 100644 programs/mousebrowser.shortcut create mode 100644 programs/npaintpro.lua create mode 100644 programs/npaintpro.shortcut create mode 100644 startup.lua create mode 100644 system/apis/flib.lua create mode 100644 system/setup.lua create mode 100644 system/skel/README.txt 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!
+An operating system for ComputerCraft.
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 CCEmuRedux or CCEmuX. 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 ] [--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 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] ") + 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 -- cgit v1.2.3