Advertisement
nitrogenfingers

micropaint

Aug 8th, 2016
2,680
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 49.91 KB | None | 0 0
  1. --[[
  2.     Micropaint
  3.     A paint program that lets you utilize the new computercraft charset for detailed painting
  4.    
  5.     Written by: Nitrogen Fingers
  6. ]]--
  7.  
  8. --[[
  9.     Operation:
  10.    
  11.     The smaller pixels allow a single ComputerCraft pixel to be divided into six subpixels, in a 3x2 pattern. For each pixel, only two colours can be used in these sub-pixels, one for the background and one for the foreground, although the program does it's best to abstract this information out when using.
  12.     As the mouse events do not have enough precision to target these sub-pixels, and each pixel can potentially have different fore and background colours, editing of these subpixels is done by selecting the desired pixel with the 'magnify' tool, or the right mouse button on the main canvas. In the smaller canvas on the top-right, a representation of that pixel is displayed in a zoomed-in fashion. Changing the colours on this magnify tool correspondingly update the larger image.
  13.     Images start filled with an alpha layer- this is filled by default with black when images are saved and loaded. When an image is 'cropped', as much of this as possible is removed.
  14.     Colours can be changed with the provided pallette. There are currently no other tools to make use of.
  15. ]]
  16. local w,h = term.getSize()
  17.    
  18. --A very quick button class, for pallette and tools
  19. local _button = {
  20.     x = nil;
  21.     y = nil;
  22.     text = nil;
  23.     colour = nil;
  24.     textcol = "f";
  25.    
  26.     draw = function(self)
  27.         term.setCursorPos(self.x, self.y)
  28.         term.blit(self.text, self.textcol:rep(#self.text), self.colour:rep(#self.text))
  29.     end;
  30.    
  31.     is_pressed = function(self, mx, my)
  32.         return mx >= self.x and mx < self.x + #self.text and my == self.y
  33.     end;
  34.    
  35.     on_pressed = function() print("not implemented yet") end;
  36. }
  37. _button.__index = function(t, k) return _button[k] end
  38. _button.new = function(x, y, text, colour)
  39.     local new = { x = x; y = y; text = text; colour = colour; }
  40.     setmetatable(new, _button)
  41.     return new
  42. end
  43.  
  44. --A kind of button that uses images rather than text
  45. local _imgbutton = {
  46.     x = nil;
  47.     y = nil;
  48.     img = nil;
  49.    
  50.     is_pressed = function(self, mx, my)
  51.         return mx >= self.x and mx <= self.x + self.img.width and
  52.                 my >= self.y and my <= self.y + self.img.height
  53.     end;
  54.    
  55.     on_pressed = function() print("not implemented yet") end;
  56.    
  57.     change_colour = function(self, old_col, new_col)
  58.         for i=1, self.img.height do
  59.             self.img.fg[i]:gsub(old_col, new_col)
  60.             self.img.bg[i]:gsub(old_col, new_col)
  61.         end
  62.     end;
  63. }
  64. _imgbutton.__index = function(t, k) return _imgbutton[k] end
  65. _imgbutton.new = function(x, y, image)
  66.     local new = { x = x, y = y, img = image }
  67.     setmetatable(new, _imgbutton)
  68.     return new
  69. end
  70.  
  71. --Draws an image format of given width and height to the screen
  72. --This won't work for our painting image, as the lines aren't concatenated
  73. local function drawImage(image, x, y)
  74.     for i = 1, image.height do
  75.         term.setCursorPos(x, y + i - 1)
  76.         term.blit(image.text[i], image.fg[i], image.bg[i])
  77.     end
  78. end
  79.  
  80. _imgbutton.draw = function(self, x, y) drawImage(self.img, self.x, self.y) end
  81.  
  82. --The full image. Contains width and height information, as well as three distinct layers, stored in a 2D array:
  83.     -- text: The character used to display the pixel. This is a char between 128 and 159, and can be nil.
  84.     -- fg: The colouring of that character
  85.     -- bg: The colouring of the background behind the character
  86. local img = { }
  87.  
  88. --A map of the magnify tool. This is a 3x2 map of single-character colour codes, integer-indexed
  89.     --Left and right are part of the painting tool.
  90.     --pixelX and pixelY are markers to the index of the img table being edited
  91. local magnify = { }
  92.  
  93. --The offset from the top left corner to draw the image
  94. local imgoffX, imgoffY = 2, 2
  95. --The offset from the top left corner to draw the magnify tool
  96. local magoffX, magoffY = 0, 0
  97. --The colour underlying the null characters
  98. local alpha = "f"
  99. --The character representing null characters
  100. local nullchar = "\127"
  101. --The maximum size of t a canvas. For the present- scrolling later perhaps
  102. local maxWidth, maxHeight = w - 12, h - 4
  103. --The minimum size of a canvas
  104. local minWidth, minHeight = 1, 1
  105.  
  106. --The animation timer used to update 'ticking' interface elements (select boxes, magnify etc. etc.)
  107. local tickState = 0
  108. --The interval for the tick timer
  109. local tickInterval = 0.5
  110. --The tick timer
  111. local tickTimer = os.startTimer(tickInterval)
  112.  
  113. --The name of the file being edited, if named
  114. local filename = nil
  115. --The file path itself
  116. local sPath = nil
  117. --Whether or not a saveable change has been made since the program started
  118. local sChange = false
  119. --Allows the run loop to proceed
  120. local programRunning = true
  121. --Whether or not the code should be shown
  122. local showMagCode = false
  123. --Whether or not the control key is down
  124. local ctrl_down = false
  125.  
  126. --The order of our pallette. Done manually just to make it look a little nicer
  127. local colourder = {"f", "7", "8", "0", "e", "a", "b", "c", "1", "2", "9", "d", "4", "6", "3", "5"}
  128. --A list of buttons that can be pressed to change the colour of the paint tools
  129. local pallette = {}
  130. --A button drawn near the paint pallette that switches colours left and right
  131. local pswitch = _button.new(0, 0, "\18", "8")
  132. --Buttons for the file and edit menu
  133. local fileButton = _button.new(2, 1, "File", "b")
  134. fileButton.textcol = "0"
  135. local editButton = _button.new(8, 1, "Edit", "b")
  136. editButton.textcol = "0"
  137. --The file and edit menu
  138. local fMenu = { name = "File", x = fileButton.x, y = fileButton.y }
  139. local eMenu = { name = "Edit", x = editButton.x, y = editButton.y }
  140. --The list of each of the tool buttons, displayed on the other side of the magnifying image
  141. local tools = {}
  142. --The currently selected tool. A string.
  143. local selTool = "paint"
  144.  
  145. --The select box details
  146. local selboxX1, selboxY1 = nil, nil
  147. local selboxX2, selboxY2 = nil, nil
  148. --Where the cursor tool is currently working from
  149. local cursorX, cursorY = nil, nil
  150. --The clipboard
  151. local clipboard = nil
  152.  
  153. --Draws a very thin 2D border around the specified rectangle. fg is the border colour, bg the background
  154. local function drawEnclosingBorder(x, y, w, h, bg, fg)
  155.     term.setCursorPos(x-1, y-1)
  156.     local topborder = "\159"..("\143"):rep(w).."\144"
  157.     term.blit(topborder, bg:rep(#topborder-1)..fg, fg:rep(#topborder-1)..bg)
  158.     for iy = y,y+h-1 do
  159.         term.setCursorPos(x-1, iy)
  160.         term.blit("\149", bg, fg)
  161.         term.setCursorPos(x+w, iy)
  162.         term.blit("\149", fg, bg)
  163.     end
  164.     term.setCursorPos(x-1, y+h)
  165.     local botborder = "\130"..("\131"):rep(w).."\129"
  166.     term.blit(botborder, fg:rep(#botborder), bg:rep(#botborder))
  167. end
  168.  
  169. --Tries to preserve the colour settings picked by the user- but changes them if necessary
  170. local function updateMagColours(fg, bg, fused)
  171.     if not fused then
  172.         if bg ~= magnify.left and bg ~= magnify.right then return magnify.left, bg
  173.         else return magnify.left, magnify.right end
  174.     else
  175.         if (bg == magnify.left or bg == magnify.right) and (fg == magnify.left or fg == magnify.right) then
  176.             return magnify.left, magnify.right
  177.         elseif bg == magnify.left then return magnify.left, fg
  178.         elseif fg == magnify.left then return magnify.left, bg
  179.         elseif bg == magnify.right then return fg, magnify.right
  180.         elseif fg == magnify.right then return bg, magnify.right
  181.         else return fg, bg end
  182.     end
  183. end
  184.  
  185. --[[The following two functions belong to the BLittle API by BombBloke, which can be viewed here:
  186.     http://www.computercraft.info/forums2/index.php?/topic/25354-cc-176-blittle-api/
  187.     For comments or question, please contact the author directly.
  188. ]]--
  189. function saveBLittle()
  190.     local output = fs.open(sPath, "wb")
  191.     if not output then error("Can't open "..output.." for output.") end
  192.    
  193.     local function writeInt(num)
  194.         output.write(bit.band(num, 255))
  195.         output.write(bit.brshift(num, 8))
  196.     end
  197.    
  198.     writeInt(img.width)
  199.     writeInt(img.height)
  200.    
  201.     local imgComponents = {"text", "fg", "bg"}
  202.     local defComponent = { "\128", "0", "f" }
  203.     for ind,i in ipairs(imgComponents) do
  204.         local thisSet = img[i]
  205.         for y = 1, img.height do
  206.             local thisRow = thisSet[y]
  207.             for x = 1, img.width do
  208.                 output.write((thisRow[x] or defComponent[ind]):byte())
  209.             end
  210.         end
  211.     end
  212.    
  213.     output.close()
  214.     sChange = false
  215. end
  216.  
  217. function loadBLittle(_filename)
  218.     local input = fs.open(_filename, "rb")
  219.     if not input then error("Can't open "..input.." for input.") end
  220.    
  221.     local function readInt()
  222.         local result = input.read()
  223.         return result + bit.blshift(input.read(), 8)
  224.     end
  225.    
  226.     local image = {}
  227.     image.width = readInt()
  228.     image.height = readInt()
  229.    
  230.     local imgComponents = {"text", "fg", "bg"}
  231.     for _,i in ipairs(imgComponents) do
  232.         local thisSet = {}
  233.         for y = 1, image.height do
  234.             local thisRow = {}
  235.             for x = 1, image.width do thisRow[x] = string.char(input.read()) end
  236.             thisSet[y] = thisRow
  237.         end
  238.         image[i] = thisSet
  239.     end
  240.    
  241.     input.close()
  242.     sChange = false
  243.    
  244.     return image
  245. end
  246.  
  247. --Takes a 3x2 map of colours and turns it into character with colours
  248. local function encodeChar(magimage)
  249.     --The binary 'score' of the iamge that determines the pixel
  250.     local score = 0
  251.     --We use the bottom-right colour as our baseline
  252.     local brcol = magimage[3][2]
  253.     local fcol = nil
  254.    
  255.     for i = 0,4 do
  256.         if magimage[math.floor(i/2)+1][(i%2)+1] ~= brcol then
  257.             score = score + math.pow(2,i)
  258.             fcol = magimage[math.floor(i/2)+1][(i%2)+1]
  259.         end
  260.     end
  261.     fcol = fcol or magnify.left
  262.     return string.char(128 + score), brcol, fcol
  263. end
  264.  
  265. --Takes a character from the image and decodes it into a 3x2 map
  266. local function decodeChar(x, y)
  267.     local chr, fg, bg = img.text[y][x], img.fg[y][x], img.bg[y][x]
  268.     local score = chr and string.byte(chr) - 128 or 0
  269.     local fused = false
  270.    
  271.     local magimage = { { }, { }, { } }
  272.     for i = 0, 4 do
  273.         if bit.band(score, math.pow(2, i)) == 0 then
  274.             magimage[math.floor(i/2)+1][(i%2)+1] = bg
  275.         else
  276.             magimage[math.floor(i/2)+1][(i%2)+1] = fg
  277.             fused = true
  278.         end
  279.     end
  280.    
  281.     magimage[3][2] = bg
  282.     magimage.left, magimage.right = updateMagColours(fg, bg, fused)
  283.     assert(magimage.left)
  284.     assert(magimage.right)
  285.     magimage.pixelX, magimage.pixelY = x, y
  286.     return magimage
  287. end
  288.  
  289. --Draws a single character from the image on the screen
  290. local function drawChar(x, y)
  291.     term.setCursorPos(imgoffX + x, imgoffY + y)
  292.     if img.bg[y][x] == nil then
  293.         term.blit(nullchar, "7", "f")
  294.     else
  295.         term.blit(img.text[y][x] and img.text[y][x] or " ",
  296.             img.fg[y][x] and img.fg[y][x] or img.bg[y][x], img.bg[y][x])
  297.     end
  298. end
  299.  
  300. --Flashes to draw attention to what pixel is currently being magnified
  301. local function drawMagnifyIcon()
  302.     if tickState % 2 == 0 and magnify[1] then
  303.         term.setCursorPos(imgoffX + magnify.pixelX, imgoffY + magnify.pixelY)
  304.         local fg = string.format("%x", 15 - tonumber(img.bg[magnify.pixelY][magnify.pixelX], 16))
  305.         local bg = img.bg[magnify.pixelY][magnify.pixelX]..""
  306.         assert(type(nullchar) == "string")
  307.         assert(type(fg) == "string")
  308.         assert(type(bg) == "string")
  309.         term.blit(nullchar, fg, bg)
  310.     elseif magnify[1] then
  311.         drawChar(magnify.pixelX, magnify.pixelY)
  312.     end
  313. end
  314.  
  315. --Draws the code used by the magnify image
  316. local function drawMagCode()
  317.     if not showMagCode then return end
  318.     term.setCursorPos(magoffX + 1, magoffY + 6)
  319.     local msg = magnify[1] and "\\"..string.byte(img.text[magnify.pixelY][magnify.pixelX]) or "    "
  320.     term.blit(msg, ("0"):rep(#msg), ("7"):rep(#msg))
  321. end
  322.  
  323. --Draws a graphic of the left and right paint colours beneath the magnify tool
  324. local function drawMagColours()
  325.     pswitch:draw()
  326.     local mcx, mcy = magoffX + 2, magoffY + 8
  327.     term.setCursorPos(mcx, mcy)
  328.     local l, r = magnify.left or alpha, magnify.right or alpha
  329.     term.blit(("\143"):rep(2).." ", "8"..r:rep(2), l:rep(2)..r)
  330.     term.setCursorPos(mcx, mcy + 1)
  331.     term.blit("  ".."\131", l:rep(2)..r, l:rep(2).."8")
  332. end
  333.  
  334. --Draws the entire magnify tool, including the colour pallette and the display
  335. local function drawMagnify()
  336.     drawMagColours()
  337.     if not magnify[1] then
  338.         for y = magoffY, magoffY + 5 do
  339.             term.setCursorPos(magoffX, y)
  340.             term.blit(nullchar:rep(6), "777777", "ffffff")
  341.         end
  342.         return
  343.     end
  344.    
  345.     for y = 1, 3 do
  346.         for x = 1, 2 do
  347.             local a, c = nullchar:rep(3), "fff"
  348.             if magnify[y][x] then a, c = "   ", magnify[y][x]:rep(3) end
  349.             term.setCursorPos(x*3 + magoffX - 3, y*2 + magoffY - 2)
  350.             term.blit(a, "777", c)
  351.             term.setCursorPos(x*3 + magoffX - 3, y*2 + 1 + magoffY - 2)
  352.             term.blit(a, "777", c)
  353.         end
  354.     end
  355. end
  356.  
  357. --Draws the selection box, if it's visible
  358. local function drawSelectionBox()
  359.     --Captures if there's no box to draw (null or too small)
  360.     if selboxX1 == selboxX2 or selboxY1 == selboxY2 then return end
  361.    
  362.     local minX, maxX = math.min(selboxX1, selboxX2), math.max(selboxX1, selboxX2)
  363.     local minY, maxY = math.min(selboxY1, selboxY2), math.max(selboxY1, selboxY2)
  364.    
  365.     local intv = (minX + minY + tickState) % 2 == 0
  366.    
  367.     if intv == 1 and clipboard and clipboard.projected then
  368.         drawClipboard()
  369.     end
  370.    
  371.     local border = ""
  372.     term.setCursorPos(minX + imgoffX, minY + imgoffY)
  373.     if intv then
  374.         border = "\145"..("\129"):rep(maxX - minX - 1).."\137"
  375.         term.blit(border, ("8"):rep(#border), ("f"):rep(#border))
  376.     else
  377.         border = "\134"..("\130"):rep(maxX - minX - 1).."\157"
  378.         term.blit(border, ("8"):rep(#border-1).."f", ("f"):rep(#border-1).."8")
  379.     end
  380.    
  381.     intv = (minX + maxY + tickState) % 2 == 0
  382.     term.setCursorPos(minX + imgoffX, maxY + imgoffY)
  383.     if intv then
  384.         border = "\145"..("\144"):rep(maxX - minX - 1).."\152"
  385.         term.blit(border, ("8"):rep(#border), ("f"):rep(#border))
  386.     else
  387.         border = "\155"..("\159"):rep(maxX - minX - 1).."\157"
  388.         term.blit(border, ("f"):rep(#border), ("8"):rep(#border))
  389.     end
  390.    
  391.    
  392.     for iy = minY + 1, maxY - 1 do
  393.         --Left side
  394.         term.setCursorPos(minX + imgoffX, iy + imgoffY)
  395.         intv = (iy + minX + tickState) % 2 == 0
  396.         term.blit(intv and "\145" or "\132", "8", "f")
  397.         --Right side
  398.         term.setCursorPos(maxX + imgoffX, iy + imgoffY)
  399.         intv = (iy + minX + tickState) % 2 == 0
  400.         term.blit(intv and "\136" or "\157", intv and "8" or "f", intv and "f" or "8")
  401.     end
  402. end
  403.  
  404. --Helper function to draw entire image to screen
  405. local function drawImage()
  406.     for y = 1, img.height do
  407.         for x = 1, img.width do
  408.             drawChar(x, y)
  409.         end
  410.     end
  411.     drawSelectionBox()
  412. end
  413.  
  414.  
  415. --Draws all the tools
  416. local function drawTools()
  417.     for k,v in pairs(tools) do
  418.         v:draw()
  419.         if k == selTool then
  420.             drawEnclosingBorder(v.x, v.y, v.img.width, v.img.height, "8", "e")
  421.         end
  422.     end
  423. end
  424.  
  425. --Updates changes to the magnify tool to the image itself
  426. local function updateImage()
  427.     img.text[magnify.pixelY][magnify.pixelX],
  428.     img.bg[magnify.pixelY][magnify.pixelX],
  429.     img.fg[magnify.pixelY][magnify.pixelX] = encodeChar(magnify)
  430.     drawMagnifyIcon()
  431.     drawMagCode()
  432. end
  433.  
  434. --Updates the magnify tool by changes made to the main canvas or colour pallette
  435. local function updateMagnify(ocol, ncol)
  436.     if not magnify[3] or ocol == magnify.left or ocol == magnify.right then  
  437.         drawMagnify()
  438.         return
  439.     end
  440.    
  441.     for y = 1, 3 do
  442.         for x = 1, 2 do
  443.             if magnify[y][x] == ocol then magnify[y][x] = ncol end
  444.         end
  445.     end
  446.     drawMagnify()
  447.     updateImage()
  448. end
  449.  
  450. --Copies all data currently under the selection. Can cut with optional argument
  451. local function copySelection(cut)
  452.     if selboxX1 == selboxX2 or selboxY1 == selboxY2 then return end
  453.     local swidth, sheight = math.abs(selboxX2 - selboxX1), math.abs(selboxY2 - selboxY1)
  454.     local minX, minY = math.min(selboxX1, selboxX2), math.min(selboxY1, selboxY2)
  455.     local maxX, maxY = math.max(selboxX1, selboxX2), math.max(selboxY1, selboxY2)
  456.     clipboard = {
  457.         width = swidth;
  458.         height = sheight;
  459.         projected = false;
  460.         text = { }; fg = { }; bg = { }; }
  461.    
  462.     for y = 1, sheight do
  463.         clipboard.text[y] = { }
  464.         clipboard.fg[y] = { }
  465.         clipboard.bg[y] = { }
  466.         for x = 1, swidth do
  467.             clipboard.text[y][x] = img.text[y + minY - 1][x + minX - 1]
  468.             clipboard.fg[y][x] = img.fg[y + minY - 1][x + minX - 1]
  469.             clipboard.bg[y][x] = img.bg[y + minY - 1][x + minX - 1]
  470.             if cut then
  471.                 img.text[y + minY - 1][x + minX - 1] = nil
  472.                 img.fg[y + minY - 1][x + minX - 1] = nil
  473.                 img.bg[y + minY - 1][x + minX - 1] = nil
  474.             end
  475.         end
  476.     end
  477. end
  478.  
  479. --Pastes data in the projected clipboard onto the screen
  480. local function pasteSelection()
  481.     if clipboard == nil or clipboard.projected ~= true then return end
  482.    
  483.     for x = 1, clipboard.width do
  484.         for y = 1, clipboard.height do
  485.            
  486.         end
  487.     end
  488. end
  489.  
  490. --The step before pasting. This recreates the selection box and has the clipboard drawn inside-
  491. --the user can then move the image around before deciding on where to place it and striking a key or
  492. --changing tools.
  493. local function projectClipboard()
  494.     if clipboard == nil then return end
  495.    
  496.     selboxX1, selboxX2 = 1, clipboard.width
  497.     selboxY1, selboxY2 = 1, clipboard.height
  498.     clipboard.projected = true
  499. end
  500.  
  501. --Applies a colour to a given index of the magnify map
  502. local function paintMagnify(x, y, col)
  503.     magnify[math.floor(y/2) + 1][math.floor(x/3) + 1] = col
  504.     drawMagnify()
  505.     updateImage()
  506. end
  507.  
  508. --Switches the left and right paint pallette.
  509. local function switchPaintColours()
  510.     local bag = magnify.left
  511.     magnify.left = magnify.right
  512.     magnify.right = bag
  513.     drawMagColours()
  514. end
  515.  
  516. --Helper methods to ensure no OOB errors
  517. local function inCanvasBounds(x, y)
  518.     return x > imgoffX and x <= imgoffX + img.width and y > imgoffY and y <= imgoffY + img.height
  519. end
  520.  
  521. local function inMagnifyBounds(x, y)
  522.     return x >= magoffX and x <= magoffX + 5 and y >= magoffY and y <= magoffY + 5
  523. end
  524.  
  525. --Draws the menu bar
  526. local function drawMenuBar()
  527.     term.setCursorPos(2,1)
  528.     term.setBackgroundColour(colours.blue)
  529.     term.clearLine()
  530.     fileButton:draw()
  531.     editButton:draw()
  532.     local title = filename or "Untitled"
  533.     term.setCursorPos(w/2 - #title/2 + 1, 1)
  534.     term.blit(title, (filename and "0" or "3"):rep(#title), ("b"):rep(#title))
  535. end
  536.  
  537. --Draws the entire scene fresh
  538. local function drawScene()
  539.     term.setBackgroundColour(colours.lightGrey)
  540.     term.clear()
  541.     --The canvas and magnify tool
  542.     drawImage()
  543.     drawMagnify()
  544.     drawTools()
  545.     for _, p in ipairs(pallette) do
  546.         p:draw()
  547.     end
  548.     drawMenuBar()
  549.     --The borders for each tool
  550.     drawEnclosingBorder(magoffX, magoffY, 6, 6, "8", "7")
  551.     drawEnclosingBorder(imgoffX + 1, imgoffY + 1, img.width, img.height, "8", "7")
  552.     drawMagCode()
  553. end
  554.  
  555. --Repositions the tools to be adjacent to the canvas. Avoids overlapping and so on.
  556. local function setToolPositions()
  557.     magoffX, magoffY = imgoffX + img.width + 4, 3
  558.     pswitch.x, pswitch.y = imgoffX + img.width + 4, magoffY + 8
  559.     for i,c in ipairs(colourder) do
  560.         pallette[i].x = imgoffX + img.width + 1 + math.ceil(i/4) * 2
  561.         pallette[i].y = magoffY + 11 + ((i-1) % 4)
  562.     end
  563.     local ind = 1
  564.     for k,v in pairs(tools) do
  565.         tools[k].x = magoffX + 9
  566.         assert(tools[k].img)
  567.         tools[k].y = 3 + (tools[k].img.height + 1) * (ind - 1)
  568.         ind = ind + 1
  569.     end
  570. end
  571.  
  572. --Helper for resizing canvases. Shifts a table's indices left
  573. local function shiftIndexes(val)
  574.     local startx = val > 0 and img.width or 1
  575.     local endx = val > 0 and 1 or img.width
  576.    
  577.     for y = 1, img.height do
  578.         for x = startx, endx, -(val/math.abs(val)) do
  579.             img.text[y][x + val] = img.text[y][x]
  580.             img.text[y][x] = nil
  581.             img.fg[y][x + val] = img.fg[y][x]
  582.             img.fg[y][x] = nil
  583.             img.bg[y][x + val] = img.bg[y][x]
  584.             img.bg[y][x] = nil
  585.         end
  586.     end
  587. end
  588.  
  589. local function resize(nw, nh, left, bottom)
  590.     local wdiff, hdiff = nw - img.width, nh - img.height
  591.     local top = hdiff - bottom
  592.     local right = wdiff - left
  593.    
  594.     if right < 0 then
  595.         for y = 1, img.height do
  596.             for x = img.width, img.width + right + 1, -1 do
  597.                 img.text[y][x] = nil
  598.                 img.fg[y][x] = nil
  599.                 img.bg[y][x] = nil
  600.             end
  601.         end
  602.     end
  603.     shiftIndexes(left)
  604.    
  605.     local tablefunc = hdiff > 0 and table.insert or table.remove
  606.     for i = 1, math.abs(top) do
  607.         tablefunc(img.text, 1, {})
  608.         tablefunc(img.fg, 1, {})
  609.         tablefunc(img.bg, 1, {})
  610.     end
  611.    
  612.     for i = 1, math.abs(bottom) do
  613.         tablefunc(img.text, #img.text, {})
  614.         tablefunc(img.fg, #img.fg, {})
  615.         tablefunc(img.bg, #img.bg, {})
  616.     end
  617.    
  618.     img.width = nw
  619.     img.height = nh
  620.    
  621.     selboxX1, selboxX2, selboxY1, selboxY2 = nil, nil, nil, nil
  622.    
  623.     setToolPositions()
  624.     drawScene()
  625. end
  626.  
  627. --Resizes the canvas to be bigger or smaller, then redraws the scene
  628. --I is the direction to preserve the piece, as an inverse numberpad.
  629. local function resizeCanvas(nw, nh, i)
  630.     local wdiff, hdiff = nw - img.width, nh - img.height
  631.     local left = wdiff - math.ceil(wdiff - (wdiff/2) * ((i-1)%3))
  632.     local bottom = math.ceil(hdiff - (hdiff/2) * math.floor((i-1)/3))
  633.    
  634.     resize(nw, nh, left, bottom)
  635. end
  636.  
  637. --Changes the image width and height to match the area captured in the
  638. local function cropToSelection()
  639.     if selboxX1 == selboxX2 or selboxY1 == selboxY2 then return end
  640.    
  641.     local xmin, xmax = math.min(selboxX1, selboxX2), math.max(selboxX1, selboxX2)
  642.     local ymin, ymax = math.min(selboxY1, selboxY2), math.max(selboxY1, selboxY2)
  643.     local nw, nh = xmax - xmin + 1, ymax - ymin + 1
  644.    
  645.     local left = 1 - xmin
  646.     local bottom = ymax - img.height
  647.    
  648.     resize(nw, nh, left, bottom)
  649. end
  650.  
  651. --An off-center print function.
  652. local function wprintOffCenter(msg, height, width, offset)
  653.     local inc = 0
  654.     local ops = 1
  655.     while #msg - ops > width do
  656.         local nextspace = 0
  657.         while string.find(msg, " ", ops + nextspace) and
  658.                 string.find(msg, " ", ops + nextspace) - ops < width do
  659.             nextspace = string.find(msg, " ", nextspace + ops) + 1 - ops
  660.         end
  661.         local ox,oy = term.getCursorPos()
  662.         term.setCursorPos(width/2 - (nextspace)/2 + offset, height + inc)
  663.         inc = inc + 1
  664.         term.write(string.sub(msg, ops, nextspace + ops - 1))
  665.         ops = ops + nextspace
  666.     end
  667.     term.setCursorPos(width/2 - #string.sub(msg, ops)/2 + offset, height + inc)
  668.     term.write(string.sub(msg, ops))
  669.    
  670.     return inc + 1
  671. end
  672.  
  673. --[[ Gets the previous file in the path, if there is one ]]--
  674. local function getPreviousDir(_path)
  675.     if _path == "" or _path == "/" then return path
  676.     else
  677.         _path = _path:reverse()
  678.         return _path:sub(_path:find("/.*")+1):reverse()
  679.     end
  680. end
  681.  
  682. --Simple confirm/deny messages
  683. local function displayConfirmDialogue(ctitle, msg, ...)
  684.     local _doffX, _doffY = 8, 5
  685.     local menuBG = colours.white
  686.     --We actually print twice- once to get the lines, second time to print proper. Easier this way.
  687.     local lines = wprintOffCenter(msg, _doffY, w - (_doffX+2) * 2, _doffX + 2)
  688.    
  689.     drawScene()
  690.    
  691.     term.setCursorPos(_doffX, _doffY - 2)
  692.     term.setBackgroundColour(colours.blue)
  693.     term.setTextColour(colours.white)
  694.     term.write(string.rep(" ", w - _doffX * 2))
  695.     term.setCursorPos(w/2 - #ctitle/2, _doffY - 2)
  696.     term.write(ctitle)
  697.     term.setTextColour(colours.black)
  698.     term.setBackgroundColour(menuBG)
  699.     term.setCursorPos(_doffX, 4)
  700.     term.write(string.rep(" ", w - _doffX * 2))
  701.     for i = _doffY, _doffY + lines do
  702.         term.setCursorPos(_doffX, i)
  703.         term.write(" "..string.rep(" ", w - (_doffX) * 2 - 2).." ")
  704.     end
  705.     wprintOffCenter(msg, _doffY, w - (_doffX+2) * 2, _doffX + 2)
  706.    
  707.     if arg then
  708.         term.setCursorPos(_doffX, _doffY + lines + 1)
  709.         term.write(string.rep(" ", w - _doffX * 2))
  710.         term.setCursorPos(_doffX, _doffY + lines + 2)
  711.         term.write(string.rep(" ", w - _doffX * 2))
  712.    
  713.         local _sspace = 0
  714.         for _k,_v in ipairs(arg) do _sspace = _sspace + #_v[1] end
  715.         _sspace = (w - (_doffX * 2) - _sspace - (2 * #arg))/(#arg)
  716.         if _sspace <= #arg - 1 then assert(false, "Too little space: needed "..(#arg - 1)..", got ".._sspace) end
  717.         term.setTextColour(colours.black)
  718.         term.setCursorPos(_doffX + 1, _doffY + lines + 1)
  719.         local _spart = false
  720.         for i=1,#arg do
  721.             _v = arg[i]
  722.             arg[i][3] = term.getCursorPos()
  723.             term.setBackgroundColour(_v[2])
  724.             term.write(" ".._v[1].." ")
  725.             local _vspace = math.floor(_sspace)
  726.             if i >= #arg/2 and not _spart then _vspace = math.ceil(_sspace) _spart = true end
  727.             local _x,_y = term.getCursorPos()
  728.             term.setCursorPos(_x + _vspace, _y)
  729.         end
  730.     end
  731.    
  732.     --In the event of a message, the player hits anything to continue
  733.     while true do
  734.         local _id,_p1,_p2,_p3 = os.pullEvent()
  735.         if (not arg or #arg == 0) and (id == "key" or id == "mouse_click" or id == "mouse_drag") then
  736.             break
  737.         elseif _id == "key" then
  738.             if _p1 == keys.enter then return 1 end
  739.             if _p1 == keys.backspace then return 2 end
  740.         elseif _id == "mouse_click" and _p3 == _doffY + lines + 1 then
  741.             for i=1,#arg do
  742.                 _v = arg[i]
  743.                 if _p2 >= _v[3] and _p2 <= _v[3] + #_v[1] + 1 then return i end
  744.             end
  745.         elseif _id == "timer" and _p1 == tickTimer then tickTimer = os.startTimer(tickInterval) end
  746.     end
  747. end
  748.  
  749. --Displays and handles events for drop-down menus
  750. local function displayDropDown(x, y, options, noTitle)
  751.     inDropDown = true
  752.     if noTitle then y = y - 1 end
  753.     local menuBG = colours.white
  754.     --Figures out the dimensions of our thing
  755.     local longestX = #options.name
  756.     for i=1,#options do
  757.         local currVal = options[i]
  758.         if type(currVal) == "table" then currVal = currVal.name end
  759.         longestX = math.max(longestX, #currVal)
  760.     end
  761.     local xOffset = math.max(0, longestX - ((w-2) - x) + 1)
  762.     local yOffset = math.max(0, #options - ((h-1) - y))
  763.    
  764.     local clickTimes = 0
  765.     local tid = nil
  766.     local selection = nil
  767.     while clickTimes < 4 do
  768.         if not noTitle then
  769.             term.setCursorPos(x-xOffset,y-yOffset)
  770.             term.setBackgroundColour(colours.lightBlue)
  771.             term.setTextColour(colours.black)
  772.             term.write(options.name.." ")
  773.         end
  774.  
  775.         for i=1,#options do
  776.             term.setCursorPos(x-xOffset, y-yOffset+i)
  777.             local currVal = options[i]
  778.             if type(currVal.value) == "table" then
  779.                 currVal.enabled = #currVal.value > 0
  780.             end
  781.            
  782.             if i==selection and clickTimes % 2 == 0 then
  783.                 term.setBackgroundColour(colours.blue)
  784.                 if options[selection].enabled then term.setTextColour(colours.white)
  785.                 else term.setTextColour(colours.grey) end
  786.             else
  787.                 term.setBackgroundColour(menuBG)
  788.                 local _tcol = colours.black
  789.                 if not currVal.enabled then _tcol = colours.lightGrey end
  790.                 term.setTextColour(_tcol)
  791.             end
  792.             if type(currVal.value) == "table" then
  793.                 term.write(currVal.name..string.rep(" ", longestX-#currVal.name))
  794.                 term.setBackgroundColour(colours.blue)
  795.                 term.setTextColour(colours.white)
  796.                 term.write(">")
  797.             else
  798.                 term.write(currVal.name..string.rep(" ", longestX-#currVal.name + 1))
  799.             end
  800.            
  801.             if (i~= selection or clickTimes %2 == 1) and currVal.key and currVal.enabled then
  802.                 term.setTextColour(colours.blue)
  803.                 term.setBackgroundColour(menuBG)
  804.                 local co = currVal.name:find(currVal.key)
  805.                 if not co then co = longestX else co = co - 1 end
  806.                 term.setCursorPos(x-xOffset+co, y-yOffset + i)
  807.                 term.write(currVal.key)
  808.             end
  809.         end
  810.        
  811.         local id, p1, p2, p3 = os.pullEvent()
  812.         if id == "timer" then
  813.             if p1 == tid then
  814.                 clickTimes = clickTimes + 1
  815.                 if clickTimes > 2 then
  816.                     break
  817.                 else
  818.                     tid = os.startTimer(0.1)
  819.                 end
  820.             elseif p1 == tickTimer then
  821.                 tickTimer = os.startTimer(tickInterval)
  822.                 tickState = (tickState + 1) % 4
  823.             end
  824.         elseif id == "key" and not tid then
  825.             if p1 == keys.leftCtrl then
  826.                 selection = ""
  827.                 break
  828.             elseif p1 == keys.down and (not selection or selection < #options) then
  829.                 selection = selection or 0
  830.                 _os = selection
  831.                 repeat selection = selection + 1
  832.                 until selection == #options + 1 or options[selection].enabled
  833.                 --if selection == #options + 1 then selection = _os end
  834.             elseif p1 == keys.up and (not selection or selection > 1) then
  835.                 selection = selection or #options + 1
  836.                 _os = selection
  837.                 repeat selection = selection - 1
  838.                 until selection == 0 or options[selection].enabled
  839.                 if selection == 0 then selection = _os end
  840.             elseif p1 == keys.enter and selection and options[selection].enabled then
  841.                 tid = os.startTimer(0.1)
  842.                 clickTimes = clickTimes - 1
  843.             end
  844.         elseif id == "mouse_click" and not tid then
  845.             local _xp, _yp = x - xOffset, y - yOffset
  846.             if p2 >= _xp and p2 <= _xp + longestX + 1 and
  847.             p3 >= _yp+1 and p3 <= _yp+#options then
  848.                 if options[p3 - _yp].enabled then
  849.                     selection = p3-(_yp)
  850.                     tid = os.startTimer(0.1)
  851.                 end
  852.             else
  853.                 selection = ""
  854.                 break
  855.             end
  856.         elseif id == "char" and not tid then
  857.             for k,v in ipairs(options) do
  858.                 if v.key and v.key:lower() == p1:lower() and options[k].enabled then
  859.                     selection = k
  860.                     tid = os.startTimer(0.1)
  861.                     break
  862.                 end
  863.             end
  864.         end
  865.     end
  866.    
  867.     local _val = selection
  868.     if type(selection) == "number" then
  869.         selection = options[selection].value
  870.     end
  871.    
  872.     if type(selection) == "string" then
  873.         inDropDown = false
  874.         return selection
  875.     elseif type(selection) == "table" then
  876.         return displayDropDown(x + longestX + 1, y + _val, selection, true)
  877.     elseif type(selection) == "function" then
  878.         local _rval = selection()
  879.         if not _rval then return "" else return _rval end
  880.     end
  881. end
  882.  
  883. --A built-in form designed to let users create new documents of a given size
  884. local function displaySizeSettings(_flag)
  885.     local title = _flag == "n" and "New Image" or "Resize Image"
  886.     local _w, _h = _flag == "n" and 16 or 24, 8
  887.     local _x, _y = math.ceil(w/2 - _w/2), 4
  888.     local _wopt, _hopt = { name = "Width:", y = 2, val = "", sel = false }, { name = "Height:", y = 4, val = "", sel = false }
  889.     local _fx, _self = 9, nil
  890.     local _fg, _bg, _hfg, _hbg = "f", "0", "0", "b"
  891.     local _by = 6
  892.     local _okay = _button.new(_x + 1, _y + _by, "Create", "5")
  893.     local _invalid = _button.new(_x + 1, _y + _by, "Invalid", "8")
  894.     _invalid.textcol = "7"
  895.     local _cancel = _button.new(_x + #_invalid.text + 2, _y + _by, "Cancel", "e")
  896.    
  897.     drawScene()
  898.    
  899.     if _flag == "r" then
  900.         _wopt.val = img.width..""
  901.         _hopt.val = img.height..""
  902.     end
  903.    
  904.     local _resizeList = { }
  905.     for i = 1, 9 do
  906.         _resizeList[i] = _button.new(_x + _w - (6 - ((i - 1) % 3) * 2), _y + math.ceil(i/3) * 2, " ", "7")
  907.         _resizeList[i].textcol = "0"
  908.     end
  909.     _resizeList[2].text = "\24"
  910.     _resizeList[4].text = "\27"
  911.     _resizeList[6].text = "\26"
  912.     _resizeList[8].text = "\25"
  913.    
  914.     _resizeList[5].colour = "5"
  915.     _rsSel = 5
  916.    
  917.     local function drawWindow()
  918.         local _ws = _w - #title - 1
  919.         term.setCursorPos(_x, _y)
  920.         term.blit((" "):rep(math.floor(_ws/2))..title..(" "):rep(math.ceil(_ws/2)).."x", ("0"):rep(_w), ("b"):rep(_w-1).."e")
  921.         term.setCursorPos(_x, _y + 1)
  922.         term.setBackgroundColour(colours.white)
  923.         term.setTextColour(colours.black)
  924.         term.write((" "):rep(_w))
  925.         term.setCursorPos(_x, _y + 2)
  926.         local _ws = _w - #_wopt.name - 1
  927.         term.write(" ".._wopt.name..(" "):rep(_ws))
  928.         term.setCursorPos(_x, _y + 3)
  929.         term.write((" "):rep(_w))
  930.         term.setCursorPos(_x, _y + 4)
  931.         local _ws = _w - #_hopt.name - 1
  932.         term.write(" ".._hopt.name..(" "):rep(_ws))
  933.         for i=5,_h-1 do
  934.             term.setCursorPos(_x, _y + i)
  935.         term.write((" "):rep(_w))
  936.         end
  937.     end
  938.    
  939.     local function valsValid()
  940.         local nw, nh = tonumber(_wopt.val), tonumber(_hopt.val)
  941.         return type(nw) == "number" and type(nh) == "number" and nw >= minWidth and nw <= maxWidth and
  942.                 nh >= minHeight and nh <= maxHeight
  943.     end
  944.    
  945.     local function drawFields()
  946.         term.setCursorPos(_x + _fx, _y + _wopt.y)
  947.         term.setBackgroundColour(colours.lightGrey)
  948.         term.setTextColour(colours.black)
  949.         term.write(_wopt.val..(" "):rep(3 - #_wopt.val))
  950.         term.setCursorPos(_x + _fx, _y + _hopt.y)
  951.         term.write(_hopt.val..(" "):rep(3 - #_hopt.val))
  952.     end
  953.    
  954.     local function updateBlink()
  955.         if _self == nil then
  956.             term.setCursorBlink(false)
  957.         else
  958.             term.setCursorPos(_x + _fx + #_self.val, _y + _self.y)
  959.             term.setCursorBlink(true)
  960.         end
  961.     end
  962.    
  963.     local function drawButtons()
  964.         term.setCursorPos(_x + 1, _y + _by)
  965.         term.setBackgroundColour(colours.white)
  966.         term.write((" "):rep(#_invalid.text))
  967.         if valsValid() then
  968.             _okay:draw()
  969.         else
  970.             _invalid:draw()
  971.         end
  972.         _cancel:draw()
  973.     end
  974.    
  975.     local function displayResizePositions()
  976.         for i,b in ipairs(_resizeList) do
  977.             b.colour = i == _rsSel and "5" or "7"
  978.             b:draw()
  979.         end
  980.     end
  981.    
  982.     drawWindow()
  983.     drawFields()
  984.     drawButtons()
  985.     if _flag == "r" then displayResizePositions() end
  986.    
  987.     _menuComplete = false
  988.     repeat
  989.         local _id, _p1, _p2, _p3 = os.pullEvent()
  990.        
  991.         if _id == "timer" then
  992.             if _p1 == tickTimer then tickTimer = os.startTimer(tickInterval) end
  993.         elseif _id == "mouse_click" then
  994.             if _cancel:is_pressed(_p2, _p3) or (_p2 == _x + _w - 1 and _p3 == _y) then
  995.                 _menuComplete = true
  996.             elseif _okay:is_pressed(_p2, _p3) and valsValid() then
  997.                 term.setCursorBlink(false)
  998.                 return tonumber(_wopt.val), tonumber(_hopt.val), _rsSel
  999.             elseif _p2 >= _x + _fx and _p2 <= _x + _fx + 3 and _p3 == _y + _wopt.y then
  1000.                 _self = _wopt
  1001.                 updateBlink()
  1002.             elseif _p2 >= _x + _fx and _p2 <= _x + _fx + 3 and _p3 == _y + _hopt.y then
  1003.                 _self = _hopt
  1004.                 updateBlink()
  1005.             elseif _flag == "r" then
  1006.                 local pressed = false
  1007.                 for i, b in ipairs(_resizeList) do
  1008.                     if b:is_pressed(_p2, _p3) then
  1009.                         _rsSel = i
  1010.                         pressed = true
  1011.                         break
  1012.                     end
  1013.                 end
  1014.                 displayResizePositions()
  1015.                
  1016.                 if not pressed then
  1017.                     _self = nil
  1018.                 end
  1019.                 updateBlink()
  1020.             else
  1021.                 _self = nil
  1022.                 updateBlink()
  1023.             end
  1024.         elseif _id == "char" then
  1025.             if _self and #_self.val < 2 then
  1026.                 _self.val = _self.val.._p1
  1027.                 drawFields()
  1028.                 drawButtons()
  1029.                 updateBlink()
  1030.             end
  1031.         elseif _id == "key" then
  1032.             if _self and _p1 == keys.backspace then
  1033.                 _self.val = _self.val:sub(0, #_self.val-1)
  1034.                 drawFields()
  1035.                 drawButtons()
  1036.                 updateBlink()
  1037.             end
  1038.         end
  1039.     until _menuComplete
  1040.     term.setCursorBlink(false)
  1041. end
  1042.  
  1043. --A more elaborate save menu than my first effort. Directory navigation and election. It works well enough.
  1044. local function displayFileBrowser(_flag,_h,_w,_y,_x)
  1045.     local _mt = { ["-s"] = "Save As", ["-b"] = "Browse Files", ["-l"] = "Load File" }
  1046.     if not _h then _h = math.floor(h/2) else _h = math.floor(_h) end
  1047.     if not _w then _w = math.floor(w/2) else _w = math.floor(_w) end
  1048.     if not _x then _x = math.floor(w/2-_w/2) + 1 else _x = math.floor(_x) end
  1049.     if not _y then _y = math.floor(h/2-_h/2) end
  1050.     local _bc,_tc = colours.white, colours.black
  1051.     local _btc,_ttc = colours.blue, colours.white
  1052.     local _bfc,_tfc,_tdc = colours.black, colours.white, colours.green
  1053.     local _fname = sName or ""
  1054.     --This is a nasty timesaver.
  1055.     local _cpath = sPath or "/"..shell.resolve(".")
  1056.     if _cpath == "/" then _cpath = "" end
  1057.     if not _cpath:find("/") then
  1058.         _cpath = ""
  1059.     elseif sPath then
  1060.         _cpath = "/"..getPreviousDir(_cpath)
  1061.     end
  1062.     local _rlist = fs.list(_cpath)
  1063.     if _cpath ~= "/" and _cpath ~= "" then table.insert(_rlist, 1, "..") end
  1064.    
  1065.     local _scr = 0
  1066.     local _abmsg = { ["-l"] = " Open ", ["-s"] = " Save As ",
  1067.             [1] = " Invalid ", [2] = " Overwrite " }
  1068.     local _labmsg = ""
  1069.     local _winh = _h - 7
  1070.     local _cpos = 0
  1071.    
  1072.     drawScene()
  1073.    
  1074.     --Some dedicated internal draw functions (to speed things up)
  1075.     local function _drawWindow()
  1076.         --Permanent parts of the window
  1077.         term.setBackgroundColour(_btc)
  1078.         term.setTextColour(_ttc)
  1079.         term.setCursorPos(_x,_y)
  1080.         term.write(string.rep(" ", math.floor(_w/2) - math.floor(#_mt[_flag]/2))
  1081.                 .._mt[_flag]..string.rep(" ", math.ceil(_w/2) -
  1082.                 math.ceil(#_mt[_flag]/2)-1))
  1083.         term.setBackgroundColour(colours.red)
  1084.         term.setTextColour(colours.white)
  1085.         term.write("x")
  1086.         term.setBackgroundColour(_bc)
  1087.         term.setTextColour(_tc)
  1088.         for i=1,_h do
  1089.             term.setCursorPos(_x,_y+i)
  1090.             term.write(string.rep(" ", _w))
  1091.         end
  1092.        
  1093.         term.setTextColour(colours.black)
  1094.         term.setBackgroundColour(colours.red)
  1095.         term.setCursorPos(_x + _w - 10, _y + _h - 1)
  1096.         term.write(" Cancel ")
  1097.     end
  1098.    
  1099.     local function _drawBrowser()
  1100.         term.setBackgroundColour(_bc)
  1101.         term.setTextColour(_tc)
  1102.         local _dpath = _cpath
  1103.         if #_dpath > _w-4 then
  1104.             while _dpath:find("/") do
  1105.                 local _ind = _dpath:find("/") + 1
  1106.                 _dpath = _dpath:sub(_ind)
  1107.                 if #_dpath < _w-7 then
  1108.                     _dpath = "...".._dpath
  1109.                     break
  1110.                 end
  1111.             end
  1112.         end
  1113.         if #_dpath > _w-4 then _dpath = "...".._dpath:sub(_w-4-#_dpath)
  1114.         elseif #_dpath == 0 then _dpath = "/" end
  1115.         term.setCursorPos(_x+2,_y+2)
  1116.         term.write(_dpath..string.rep(" ", _w-4-#_dpath))
  1117.        
  1118.         term.setBackgroundColour(_bfc)
  1119.         for i = 1 + _scr, _winh + _scr do
  1120.             _pth = _rlist[i] or ""
  1121.             if #_pth > _w - 5 then _pth = _pth:sub(1,(_w - 8)).."..." end
  1122.             term.setCursorPos(_x + 2, _y + 2 + i - _scr)
  1123.             if fs.isDir(_cpath.."/".._pth) then
  1124.                 term.setTextColour(_tdc)
  1125.             else term.setTextColour(_tfc) end
  1126.             term.write(" ".._pth..string.rep(" ", _w - 5 - #_pth))
  1127.         end
  1128.     end
  1129.    
  1130.     local function _drawActivationButton()
  1131.         _owrite = 0
  1132.         local _val = _cpath.."/".._fname
  1133.         if (not fs.exists(_val) and _flag == "-l") or
  1134.                 (fs.exists(_val) and fs.isDir(_val)) then
  1135.             _owrite = 1
  1136.         elseif fs.exists(_val) and _flag == "-s" then _owrite = 2 end
  1137.         term.setTextColour(colours.black)
  1138.         term.setCursorPos(_x + 2, _y + _h - 1)
  1139.         if _owrite == 1 and _flag ~= "-b" then
  1140.             term.setBackgroundColour(colours.lightGrey)
  1141.             term.setTextColour(colours.grey)
  1142.             _labmsg = " Invalid "
  1143.         elseif _owrite == 2 then
  1144.             term.setBackgroundColour(colours.orange)
  1145.             _labmsg = " Overwrite "
  1146.         elseif _flag == "-s" then
  1147.             term.setBackgroundColour(colours.green)
  1148.             _labmsg = " Save "
  1149.         elseif _flag == "-l" then
  1150.             term.setBackgroundColour(colours.green)
  1151.             _labmsg = " Open "
  1152.         end
  1153.         term.write(_labmsg)
  1154.         term.setBackgroundColour(_bc)
  1155.         term.write(string.rep(" ", #" Overwrite " - #_labmsg))
  1156.     end
  1157.    
  1158.     local function _drawWriteBar()
  1159.         term.setCursorPos(_x + 2, _y + _h - 3)
  1160.         term.setTextColour(colours.black)
  1161.         term.setBackgroundColour(colours.white)
  1162.         local _pm = "Save As "
  1163.         if _flag == "-l" then _pm = "Open " end
  1164.         term.write(_pm)
  1165.        
  1166.         local _msg = _fname
  1167.         if #_msg > _w - 8 - #_pm then _msg = _msg:sub(#_msg - (_w - 8 - #_pm)) end
  1168.         term.setBackgroundColour(colours.lightGrey)
  1169.         term.write(" ".._msg)
  1170.         _cpos = term.getCursorPos()
  1171.         term.write(string.rep(" ", math.max(_w - 5 - #_pm - #_msg, 1)))
  1172.     end
  1173.    
  1174.     _drawWindow()
  1175.     _drawActivationButton()
  1176.     _drawBrowser()
  1177.     _drawWriteBar()
  1178.    
  1179.     while true do
  1180.         term.setTextColour(colours.black)
  1181.         term.setCursorPos(_cpos, _y + _h - 3)
  1182.         term.setCursorBlink(true)
  1183.         local _id,_p1,_p2,_p3 = os.pullEvent()
  1184.        
  1185.         if _id == "key" then
  1186.             if _p1 == keys.backspace and #_fname > 0 then
  1187.                 _fname = _fname:sub(1,#_fname-1)
  1188.                 _drawActivationButton()
  1189.                 _drawWriteBar()
  1190.             elseif _p1 == keys.up and _scr > 0 then
  1191.                 _scr = _scr - 1
  1192.                 _drawBrowser()
  1193.             elseif _p1 == keys.down and _scr < #_rlist - _winh then
  1194.                 _scr = _scr + 1
  1195.                 _drawBrowser()
  1196.             elseif _p1 == keys.enter then
  1197.                 local _val = _cpath.."/".._fname
  1198.                 if (_flag == "-l" and fs.exists(_val) and not fs.isDir(_val))
  1199.                         or(_flag == "-s" and not fs.isDir(_val)) then
  1200.                     break
  1201.                 end
  1202.             end
  1203.         elseif _id == "char" then
  1204.             _fname = _fname.._p1
  1205.             _drawActivationButton()
  1206.             _drawWriteBar()
  1207.         elseif _id == "mouse_click" then
  1208.             if _p2 == _x + _w - 1 and _p3 == _y then
  1209.                 _fname = nil
  1210.                 break
  1211.             elseif _p2 >= _x + 2 and _p2 <= _x + _w - 2 and _p3 >= _y + 3 and
  1212.                     _p3 < _y + 3 + _winh then
  1213.                 _p3 = _p3 - _y - 2
  1214.                 local _val = _rlist[_p3 + _scr]
  1215.                 if _val == ".." then
  1216.                     --Wow why is there no reverse find
  1217.                     _cpath = getPreviousDir(_cpath)
  1218.                     _rlist = fs.list(_cpath)
  1219.                     if _cpath ~= "/" and _cpath ~= "" then table.insert(_rlist, 1, "..") end
  1220.                     _scr = 0
  1221.                     _drawBrowser()
  1222.                     _drawActivationButton()
  1223.                 elseif fs.isDir(_cpath.."/".._val) then
  1224.                     if _cpath == "/" then _cpath = _cpath.._val
  1225.                     else _cpath = _cpath.."/".._val end
  1226.                     _rlist = fs.list(_cpath)
  1227.                     if _cpath ~= "/" and  _cpath ~= "" then table.insert(_rlist, 1, "..") end
  1228.                     _scr = 0
  1229.                     _drawBrowser()
  1230.                     _drawActivationButton()
  1231.                 else
  1232.                     _fname = _val or _fname
  1233.                     _drawActivationButton()
  1234.                     _drawWriteBar()
  1235.                 end
  1236.             elseif _p3 == _y + _h - 1 and _p2 >= _x + 2 and _p2 < _x + 2 + #        _labmsg and _labmsg ~= _abmsg[1] then
  1237.                 break
  1238.             elseif _p3 == _y + _h - 1 and _p2 >= _x + _w - 2 - #" Cancel " and
  1239.                     _p2 < _x + _w - 2 then
  1240.                 _fname = nil
  1241.                 term.setCursorBlink(false)
  1242.                 return false
  1243.             end
  1244.         elseif _id == "mouse_scroll" then
  1245.             _scr = math.min(_scr, #_rlist - _winh - 1)
  1246.             _scr = math.max(_scr + _p1, 0)
  1247.             _drawBrowser()
  1248.         elseif _id == "timer" and _p1 == tickTimer then
  1249.             tickTimer = os.startTimer(tickInterval)
  1250.         end
  1251.     end
  1252.     term.setCursorBlink(false)
  1253.     if _fname then return (_cpath.."/".._fname):sub(2),_fname else return nil end
  1254. end
  1255.  
  1256. --Sets up canvas features etc. for a loaded image
  1257. local function createLoadedImage(path)
  1258.     path = path or sPath
  1259.     img = loadBLittle(path)
  1260.  
  1261.     magnify = {
  1262.         left = "0";
  1263.         right = "f";
  1264.         pixelX = nil;
  1265.         pixelY = nil;
  1266.     }
  1267.     setToolPositions()
  1268.    
  1269.     drawScene()
  1270. end
  1271.  
  1272. --Starts a blank canvas.
  1273. local function createNewImage(iw, ih)
  1274.     img = {
  1275.         width = iw or 20;
  1276.         height = ih or 15;
  1277.        
  1278.         text = { };
  1279.         fg = { };
  1280.         bg = { };
  1281.     }
  1282.  
  1283.     for y = 1, img.height do
  1284.         img.text[y] = { }
  1285.         img.fg[y] = { }
  1286.         img.bg[y] = { }
  1287.     end
  1288.  
  1289.     magnify = {
  1290.         left = "0";
  1291.         right = "f";
  1292.         pixelX = nil;
  1293.         pixelY = nil;
  1294.     }
  1295.     setToolPositions()
  1296.    
  1297.     sPath = nil
  1298.     sChange = false
  1299.     filename = nil
  1300.    
  1301.     drawScene()
  1302. end
  1303.  
  1304. --Save a document from the menu
  1305. local function menuSaveAs()
  1306.     local _sp,sn = displayFileBrowser("-s", 14, 24, 3)
  1307.     if _sp then
  1308.         sPath = _sp
  1309.         sName = _sn
  1310.         saveBLittle(img, sPath)
  1311.     end
  1312. end
  1313.  
  1314.  
  1315. --Checks to ensure no changes have been made that are unsaved
  1316. local function checkSave()
  1317.     if not sChange then return true end
  1318.     drawScene()
  1319.     local _val = displayConfirmDialogue("Unsaved Changes", "Your document contains unsaved changes. Are you sure you want to do this?", { [1] = "Save"; [2] = colours.green }, { [1] = "Discard"; [2] = colours.orange; }, { [1] = "Cancel";
  1320.     [2] = colours.red; })
  1321.     if _val == 1 then
  1322.         if sPath then saveBLittle()
  1323.         else menuSaveAs() end
  1324.         return true
  1325.     elseif _val == 2 then
  1326.         return true
  1327.     else
  1328.         return false
  1329.     end
  1330. end
  1331.  
  1332. --Handles the events for the program
  1333. local function run()
  1334.     while programRunning do
  1335.         local id, p1, p2, p3 = os.pullEvent()
  1336.         if id == "key" then
  1337.             if p1 == keys.leftCtrl then
  1338.                 ctrl_down = true
  1339.             elseif ctrl_down then
  1340.                 local found = false
  1341.                 for _, option in ipairs(fMenu) do
  1342.                     if p1 == keys[string.lower(option.key or "-")] and option.enabled then
  1343.                         found = true
  1344.                         option.value()
  1345.                         drawScene()
  1346.                         break
  1347.                     end
  1348.                 end
  1349.                 if not found then for _, option in ipairs(eMenu) do
  1350.                     if p1 == keys[string.lower(option.key or "-")] and option.enabled then
  1351.                         found = true
  1352.                         option.value()
  1353.                         drawScene()
  1354.                         break
  1355.                     end
  1356.                 end end
  1357.                 ctrl_down = false
  1358.             end
  1359.         elseif id == "key_up" then
  1360.             if p1 == keys.leftCtrl then
  1361.                 ctrl_down = false
  1362.             end
  1363.         elseif id == "timer" then
  1364.             if p1 == tickTimer then
  1365.                 tickTimer = os.startTimer(tickInterval)
  1366.                 tickState = (tickState + 1) % 4
  1367.                 drawMagnifyIcon()
  1368.                 drawSelectionBox()
  1369.             end
  1370.         elseif id == "mouse_click" or id == "mouse_drag" then
  1371.             local ix, iy = p2 - imgoffX, p3 - imgoffY
  1372.             local mx, my = p2 - magoffX, p3 - magoffY
  1373.             if p1 == 1 then
  1374.                 --Canvas
  1375.                 if inCanvasBounds(p2, p3) then
  1376.                     if selTool == "paint" then
  1377.                         img.text[iy][ix] = "\128"
  1378.                         img.fg[iy][ix] = magnify.left
  1379.                         img.bg[iy][ix] = magnify.left
  1380.                         drawChar(ix, iy)
  1381.                         if ix == magnify.pixelX and iy == magnify.pixelY then
  1382.                             magnify = decodeChar(ix, iy)
  1383.                             drawMagnify()
  1384.                             drawMagCode()
  1385.                         end
  1386.                         sChange = true
  1387.                     elseif selTool == "magnify" and img.text[iy][ix] ~= nil then
  1388.                         local ox, oy = magnify.pixelX, magnify.pixelY
  1389.                         magnify = decodeChar(ix, iy)
  1390.                         drawMagnify()
  1391.                         drawMagCode()
  1392.                         if ox and oy then drawChar(ox, oy) end
  1393.                         drawMagnifyIcon()
  1394.                     elseif selTool == "selectbox" then
  1395.                         if id == "mouse_click" then
  1396.                             selboxX1, selboxY1 = ix, iy
  1397.                         end
  1398.                         selboxX2, selboxY2 = ix, iy
  1399.                         drawImage()
  1400.                     elseif selTool == "cursor" then
  1401.                         if id == "mouse_drag" and selboxX1 ~= selboxX2 and selboxY1 ~= selboxY2 then
  1402.                             local diffX, diffY = ix - cursorX, iy - cursorY
  1403.                             local bwidth, bheight = math.abs(selboxX2 - selboxX1), math.abs(selboxY2 - selboxY1)
  1404.                             if selboxX2 then
  1405.                                 selboxX2 = math.max(math.min(selboxX2 + diffX, img.width), bwidth + 1)
  1406.                                 selboxX1 = math.max(math.min(selboxX1 + diffX, img.width - bwidth ), 1)
  1407.                                 selboxY2 = math.max(math.min(selboxY2 + diffY, img.height), bheight + 1)
  1408.                                 selboxY1 = math.max(math.min(selboxY1 + diffY, img.height - bheight), 1)
  1409.                                 drawImage()
  1410.                             end
  1411.                         end
  1412.                         cursorX, cursorY = ix, iy
  1413.                     end
  1414.                 elseif magnify[1] and inMagnifyBounds(p2, p3) then
  1415.                     paintMagnify(mx, my, magnify.left)
  1416.                     sChange = true
  1417.                 elseif pswitch:is_pressed(p2, p3) then pswitch:on_pressed()
  1418.                 elseif fileButton:is_pressed(p2, p3) then fileButton:on_pressed()
  1419.                 elseif editButton:is_pressed(p2, p3) then editButton:on_pressed()
  1420.                 else
  1421.                     local _pressed = false
  1422.                     for _, cbutton in ipairs(pallette) do
  1423.                         if cbutton:is_pressed(p2, p3) then
  1424.                             cbutton:on_pressed(p1)
  1425.                             _pressed = true
  1426.                             break
  1427.                         end
  1428.                     end
  1429.                     --if _pressed then break end
  1430.                     for _, tbutton in pairs(tools) do
  1431.                         if tbutton:is_pressed(p2, p3) then
  1432.                             tbutton:on_pressed()
  1433.                             _pressed = true
  1434.                             break
  1435.                         end
  1436.                     end
  1437.                 end
  1438.             elseif p1 == 2 then
  1439.                 if inCanvasBounds(p2, p3) then
  1440.                     if selTool == "paint" then
  1441.                         img.text[iy][ix] = "\128"
  1442.                         img.fg[iy][ix] = magnify.right
  1443.                         img.bg[iy][ix] = magnify.right
  1444.                         drawChar(ix, iy)
  1445.                         if ix == magnify.pixelX and iy == magnify.pixelY then
  1446.                             magnify = decodeChar(ix, iy)
  1447.                             drawMagnify()
  1448.                             drawMagCode()
  1449.                         end
  1450.                         sChange = true
  1451.                     elseif selTool == "magnify" and img.text[iy][ix] ~= nil  then
  1452.                         local ox, oy = magnify.pixelX, magnify.pixelY
  1453.                         magnify = decodeChar(ix, iy)
  1454.                         drawMagnify()
  1455.                         drawMagCode()
  1456.                         if ox and oy then drawChar(ox, oy) end
  1457.                         drawMagnifyIcon()
  1458.                     elseif selTool == "selectbox" then
  1459.                        
  1460.                     elseif selTool == "cursor" then
  1461.                    
  1462.                     end
  1463.                 elseif magnify[1] and inMagnifyBounds(p2, p3) then
  1464.                     paintMagnify(mx, my, magnify.right)
  1465.                     sChange = true
  1466.                 else
  1467.                     local bpressed = false
  1468.                     for _, cbutton in ipairs(pallette) do
  1469.                         if cbutton:is_pressed(p2, p3) then
  1470.                             cbutton:on_pressed(p1)
  1471.                             bpressed = true
  1472.                             break
  1473.                         end
  1474.                     end
  1475.                    
  1476.                     if not bpressed and id ~= "mouse_drag" then
  1477.                         local ox, oy = magnify.pixelX, magnify.pixelY
  1478.                         magnify = { left = magnify.left, right = magnify.right }
  1479.                         drawMagnify()
  1480.                         drawMagCode()
  1481.                         if ox and oy then drawChar(ox, oy) end
  1482.                     end
  1483.                 end
  1484.             end
  1485.         elseif id == "mouse_scroll" then
  1486.             switchPaintColours()
  1487.         end
  1488.        
  1489.         if selTool == "paint" or selTool == "magnify" and selboxX1 then
  1490.             selboxX1, selboxY1, selboxX2, selboxY2 = nil, nil, nil
  1491.             drawImage()
  1492.         end
  1493.     end
  1494. end
  1495.  
  1496. --Interface specifications etc.
  1497. local pallette_pressed = function(self, mb)
  1498.     local ocol = (mb == 1 and magnify.left or magnify.right)
  1499.     if mb == 1 then magnify.left = self.colour
  1500.     else magnify.right = self.colour end
  1501.     updateMagnify(ocol, self.colour)
  1502. end
  1503. for i,c in ipairs(colourder) do
  1504.     nb = _button.new(w-1, i, "  ", c)
  1505.     nb.on_pressed = pallette_pressed
  1506.     table.insert(pallette, nb)
  1507. end
  1508. pswitch.on_pressed = switchPaintColours
  1509.  
  1510. fileButton.on_pressed = function()
  1511.     fMenu[3].enabled = sChange and sPath
  1512.     fMenu[4].enabled = sChange and sPath
  1513.     displayDropDown(fMenu.x, fMenu.y, fMenu)
  1514.     drawScene()
  1515. end
  1516. editButton.on_pressed = function()
  1517.     displayDropDown(eMenu.x, eMenu.y, eMenu)
  1518.     drawScene()
  1519. end
  1520.  
  1521. table.insert(fMenu, { name = "New", key = "N", enabled = true, value = function()
  1522.     if checkSave() then
  1523.         local nw, nh = displaySizeSettings("n")
  1524.         if nw and nh then createNewImage(nw, nh)
  1525.         end end end })
  1526. table.insert(fMenu, { name = "Open", key = "O", enabled = true, value = function()
  1527.     if checkSave() then
  1528.         local _sp,_sn = displayFileBrowser("-l", 14, 24, 3)
  1529.         if _sp then
  1530.             createLoadedImage(_sp)
  1531.             sPath = _sp
  1532.             filename = _sn
  1533.             assert(sPath)
  1534.     end end end })
  1535. table.insert(fMenu, { name = "Revert", key = "R", enabled = false, value = function()
  1536.     createLoadedImage(sPath)
  1537. end })
  1538. table.insert(fMenu, { name = "Save", key = "S", enabled = false, value = saveBLittle })
  1539. table.insert(fMenu, { name = "Save As", key = "A", enabled = true, value = menuSaveAs })
  1540. table.insert(fMenu, { name = "-------", enabled = false })
  1541. --table.insert(fMenu, { name = "About", key = "b", enabled = false, value = nil })
  1542. table.insert(fMenu, { name = "Quit", key = "Q", enabled = true, value = function()
  1543.     if checkSave() then programRunning = false end end })
  1544.  
  1545. table.insert(eMenu, { name = "Cut", key = 'x', enabled = false })
  1546. table.insert(eMenu, { name = "Copy", key = 'C', enabled = false })
  1547. table.insert(eMenu, { name = "Paste", key = 'v', enabled = false })
  1548. table.insert(eMenu, { name = "---------", enabled = false })
  1549. table.insert(eMenu, { name = "Resize", key = "R", enabled = true, value = function()
  1550.     local nw, nh, i = displaySizeSettings("r")
  1551.     if nw and nh then resizeCanvas(nw, nh, i) end
  1552. end})
  1553. table.insert(eMenu, { name = "Crop", key = "p", enabled = true, value = cropToSelection })
  1554. table.insert(eMenu, { name = "Show Code", enabled = true, value = function()
  1555.         showMagCode = not showMagCode
  1556.         eMenu[#eMenu].name = showMagCode and "Hide Code" or "Show Code"
  1557.     end
  1558. })
  1559.  
  1560. --A list of the images we use for each tool
  1561. local toolImages = {
  1562.     paint = {
  1563.         width = 3, height = 2,
  1564.         text = { "\128\135\135", "\142\133\128" },
  1565.         fg = { "00c", "ff0" },
  1566.         bg = { "0c0", "000" }
  1567.     },
  1568.     magnify = {
  1569.         width = 3, height = 2,
  1570.         text = { "\151\128\148", "\128\131\146" },
  1571.         fg = { "033", "090" },
  1572.         bg = { "930", "00f" }
  1573.     },
  1574.     selectbox = {
  1575.         width = 3, height = 2,
  1576.         text = { "\136\136\144", "\130\132\132" },
  1577.         fg = { "888", "888" },
  1578.         bg = { "000", "000" }
  1579.     },
  1580.     cursor = {
  1581.         width = 3, height = 2,
  1582.         text = { "\128\143\144", "\128\135\132" },
  1583.         fg = { "00f", "0ff" },
  1584.         bg = { "0f0", "000" }
  1585.     }
  1586. }
  1587.  
  1588. for k,v in pairs(toolImages) do
  1589.     local newButton = _imgbutton.new(1, 1, v)
  1590.     newButton.name = k
  1591.     newButton.on_pressed = function(self)
  1592.         if tools[selTool] then tools[selTool]:change_colour("4", "0") end
  1593.         local oldSel = tools[selTool]
  1594.         drawEnclosingBorder(oldSel.x, oldSel.y, oldSel.img.width, oldSel.img.height, "8", "8")
  1595.        
  1596.         selTool = self.name
  1597.         self:change_colour("0", "4")
  1598.         drawTools()
  1599.     end
  1600.     tools[k] = newButton
  1601. end
  1602.  
  1603. local tArgs = {...}
  1604. if tArgs[1] then
  1605.     filename = tArgs[1]
  1606.     sPath = shell.resolve(tArgs[1])
  1607.     if fs.exists(sPath) then
  1608.         createLoadedImage(sPath)
  1609.     else
  1610.         createNewImage(10,10)
  1611.     end
  1612. else
  1613.     createNewImage(10,10)
  1614. end
  1615.  
  1616. run()
  1617. term.setBackgroundColour(colours.black)
  1618. shell.run("clear")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement