mirror of
https://github.com/ctruLua/ctruLua.git
synced 2025-10-27 16:39:29 +00:00
Added text editor, keyboard lib and stuff in Lua
This commit is contained in:
parent
800dd6f963
commit
6a26be4b43
5 changed files with 408 additions and 64 deletions
6
sdcard/3ds/ctruLua/editor/color.lua
Normal file
6
sdcard/3ds/ctruLua/editor/color.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-- Colors based on the Monokai theme
|
||||
return {
|
||||
background = 0x272822FF,
|
||||
default = 0xF8F8F2FF,
|
||||
cursor = 0xFF0000FF
|
||||
}
|
||||
153
sdcard/3ds/ctruLua/editor/main.lua
Normal file
153
sdcard/3ds/ctruLua/editor/main.lua
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
local ctr = require("ctr")
|
||||
local hid = require("ctr.hid")
|
||||
local gfx = require("ctr.gfx")
|
||||
|
||||
-- Open libs
|
||||
local keyboard = dofile("sdmc:/3ds/ctruLua/keyboard.lua")
|
||||
local openfile = dofile("sdmc:/3ds/ctruLua/openfile.lua")
|
||||
local color = dofile("sdmc:/3ds/ctruLua/editor/color.lua")
|
||||
|
||||
-- Open file
|
||||
local path, status = openfile("Choose a file to edit", "/3ds/ctruLua/", nil, "any")
|
||||
if not path then return end
|
||||
local lines = {}
|
||||
if status == "exist" then
|
||||
for line in io.lines(path) do table.insert(lines, line) end
|
||||
else
|
||||
lines = { "" }
|
||||
end
|
||||
|
||||
-- Variables
|
||||
local lineHeight = 10
|
||||
local cursorX, cursorY = 1, 1
|
||||
local scrollX, scrollY = 0, 0
|
||||
|
||||
-- Set defaults
|
||||
gfx.set3D(false)
|
||||
gfx.color.setDefault(color.default)
|
||||
gfx.color.setBackground(color.background)
|
||||
|
||||
while ctr.run() do
|
||||
hid.read()
|
||||
local keys = hid.keys()
|
||||
|
||||
-- Keys input
|
||||
if keys.down.start then return end
|
||||
|
||||
if keys.down.dRight then
|
||||
cursorX = cursorX + 1
|
||||
if cursorX > utf8.len(lines[cursorY])+1 then
|
||||
if cursorY < #lines then
|
||||
cursorX, cursorY = 1, cursorY + 1
|
||||
else
|
||||
cursorX = cursorX - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if keys.down.dLeft then
|
||||
if cursorX > utf8.len(lines[cursorY])+1 then cursorX = utf8.len(lines[cursorY])+1 end
|
||||
cursorX = cursorX - 1
|
||||
if cursorX < 1 then
|
||||
if cursorY > 1 then
|
||||
cursorX, cursorY = utf8.len(lines[cursorY-1])+1, cursorY - 1
|
||||
else
|
||||
cursorX = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if keys.down.dUp and cursorY > 1 then cursorY = cursorY - 1 end
|
||||
if keys.down.dDown and cursorY < #lines then cursorY = cursorY + 1 end
|
||||
|
||||
if keys.held.cpadRight or keys.held.a then scrollX = scrollX + 3 end
|
||||
if keys.held.cpadLeft or keys.held.y then scrollX = scrollX - 3 end
|
||||
if keys.held.cpadUp or keys.held.x then scrollY = scrollY - 3 end
|
||||
if keys.held.cpadDown or keys.held.b then scrollY = scrollY + 3 end
|
||||
|
||||
if keys.down.select then
|
||||
local file = io.open(path, "w")
|
||||
if not file then
|
||||
local t = os.time()
|
||||
repeat
|
||||
gfx.startFrame(gfx.GFX_TOP)
|
||||
gfx.text(3, 3, "Can't open file in write mode")
|
||||
gfx.endFrame()
|
||||
gfx.render()
|
||||
until t + 5 < os.time()
|
||||
else
|
||||
for i = 1, #lines, 1 do
|
||||
file:write(lines[i].."\n")
|
||||
gfx.startFrame(gfx.GFX_TOP)
|
||||
gfx.rectangle(0, 0, math.ceil(i/#lines*gfx.TOP_WIDTH), gfx.TOP_HEIGHT, 0, 0xFFFFFFFF)
|
||||
gfx.color.setDefault(0x000000FF)
|
||||
gfx.text(gfx.TOP_WIDTH/2, gfx.TOP_HEIGHT/2, math.ceil(i/#lines*100).."%")
|
||||
gfx.color.setDefault(color.default)
|
||||
gfx.endFrame()
|
||||
gfx.render()
|
||||
end
|
||||
file:flush()
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
-- Keyboard input
|
||||
local input = keyboard.read()
|
||||
if input then
|
||||
if input == "BACK" then
|
||||
if cursorX > utf8.len(lines[cursorY])+1 then cursorX = utf8.len(lines[cursorY])+1 end
|
||||
if cursorX > 1 then
|
||||
lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX-1)-1)..
|
||||
lines[cursorY]:sub(utf8.offset(lines[cursorY], cursorX), -1)
|
||||
cursorX = cursorX - 1
|
||||
elseif cursorY > 1 then
|
||||
cursorX, cursorY = utf8.len(lines[cursorY-1])+1, cursorY - 1
|
||||
lines[cursorY] = lines[cursorY]..lines[cursorY+1]
|
||||
table.remove(lines, cursorY+1)
|
||||
end
|
||||
elseif input == "\n" then
|
||||
local newline = lines[cursorY]:sub(utf8.offset(lines[cursorY], cursorX), -1)
|
||||
local whitespace = lines[cursorY]:match("^%s+")
|
||||
if whitespace then newline = whitespace .. newline end
|
||||
|
||||
lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX)-1)
|
||||
table.insert(lines, cursorY + 1, newline)
|
||||
|
||||
cursorX, cursorY = whitespace and #whitespace+1 or 1, cursorY + 1
|
||||
else
|
||||
lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX)-1)..input..
|
||||
lines[cursorY]:sub(utf8.offset(lines[cursorY], cursorX), -1)
|
||||
cursorX = cursorX + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw
|
||||
gfx.startFrame(gfx.GFX_TOP)
|
||||
|
||||
local sI = math.floor(scrollY / lineHeight)
|
||||
if sI < 1 then sI = 1 end
|
||||
|
||||
local eI = math.ceil((scrollY + gfx.TOP_HEIGHT) / lineHeight)
|
||||
if eI > #lines then eI = #lines end
|
||||
|
||||
for i = sI, eI, 1 do
|
||||
local line = lines[i]
|
||||
local y = -scrollY+ (i-1)*lineHeight
|
||||
|
||||
if cursorY == i then
|
||||
gfx.color.setDefault(color.cursor)
|
||||
gfx.text(-scrollX, y, line:sub(1, (utf8.offset(line, cursorX) or 0)-1):gsub("\t", " ").."|", nil) -- TODO: color doesn't work
|
||||
gfx.color.setDefault(color.default)
|
||||
end
|
||||
|
||||
gfx.text(-scrollX, y, line:gsub("\t", " "), nil)
|
||||
end
|
||||
|
||||
gfx.endFrame()
|
||||
|
||||
gfx.startFrame(gfx.GFX_BOTTOM)
|
||||
|
||||
keyboard.draw(5, 115)
|
||||
|
||||
gfx.endFrame()
|
||||
|
||||
gfx.render()
|
||||
end
|
||||
106
sdcard/3ds/ctruLua/keyboard.lua
Normal file
106
sdcard/3ds/ctruLua/keyboard.lua
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
local hid = require("ctr.hid")
|
||||
local gfx = require("ctr.gfx")
|
||||
|
||||
-- Options
|
||||
local keyWidth, keyHeight = 25, 25
|
||||
local layout = {
|
||||
["default"] = {
|
||||
{ "&", "é", "\"", "'", "(", "-", "è", "_", "ç", "à", ")", "=", "Back" },
|
||||
{ "a", "z", "e", "r", "t", "y", "u", "i", "o", "p", "^", "$", "Enter" },
|
||||
{ "q", "s", "d", "f", "g", "h", "j", "k", "l", "m", "ù", "*", "Enter" },
|
||||
{ "Shift", "<", "w", "x", "c", "v", "b", "n", ",", ";", ":", "!", "Tab" },
|
||||
{ "CpLck", ">", "+", "/", " ", " ", " ", " ", " ", "{", "}", ".", "AltGr" }
|
||||
},
|
||||
["Shift"] = {
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "°", "+", "Back" },
|
||||
{ "A", "Z", "E", "R", "T", "Y", "U", "I", "O", "P", "¨", "£", "Enter" },
|
||||
{ "Q", "S", "D", "F", "G", "H", "J", "K", "L", "M", "%", "µ", "Enter" },
|
||||
{ "Shift", ">", "W", "X", "C", "V", "B", "N", "?", ".", "/", "§", "Tab" },
|
||||
{ "CpLck", "~", "#", "[", " ", " ", " ", " ", " ", "]", "|", "@", "AltGr" }
|
||||
},
|
||||
["AltGr"] = {
|
||||
{ "²", "~", "#", "{", "[", "|", "`", "\\", "^", "@", "]", "}", "Back" },
|
||||
{ "a", "z", "€", "r", "t", "y", "u", "i", "o", "p", "", "¤", "Enter" },
|
||||
{ "q", "s", "d", "f", "g", "h", "j", "k", "l", "m", "", "", "Enter" },
|
||||
{ "Shift", "", "w", "x", "c", "v", "b", "n", "", "", "", "", "Tab" },
|
||||
{ "CpLck", "", "", "", " ", " ", " ", " ", " ", "", "", "", "AltGr" }
|
||||
},
|
||||
}
|
||||
local alias = {
|
||||
["Tab"] = "\t",
|
||||
["Enter"] = "\n",
|
||||
["Back"] = "BACK"
|
||||
}
|
||||
local sticky = {
|
||||
["CpLck"] = "Shift"
|
||||
}
|
||||
local keys = {
|
||||
["l"] = "Shift",
|
||||
["r"] = "Shift"
|
||||
}
|
||||
|
||||
-- Variables
|
||||
local currentModifier = { "default", "sticky" }
|
||||
local buffer = ""
|
||||
|
||||
return {
|
||||
draw = function(x, y)
|
||||
local hidKeys = hid.keys()
|
||||
|
||||
local xTouch, yTouch
|
||||
if hidKeys.down.touch then xTouch, yTouch = hid.touch() end
|
||||
|
||||
for key, modifier in pairs(keys) do
|
||||
if hidKeys.down[key] then
|
||||
currentModifier = { modifier, "key" }
|
||||
elseif hidKeys.up[key] and currentModifier[2] == "key" and currentModifier[1] == modifier then
|
||||
currentModifier = { "default", "sticky" }
|
||||
end
|
||||
end
|
||||
|
||||
for row, rowKeys in pairs(layout[currentModifier[1]]) do
|
||||
for column, key in pairs(rowKeys) do
|
||||
local xKey, yKey = x + (column-1)*(keyWidth-1), y + (row-1)*(keyHeight-1)
|
||||
|
||||
gfx.rectangle(xKey, yKey, keyWidth, keyHeight, 0, 0xFFFFFFFF)
|
||||
gfx.rectangle(xKey + 1, yKey + 1, keyWidth - 2, keyHeight - 2, 0, 0x000000FF)
|
||||
gfx.text(xKey + 2, yKey + 2, key)
|
||||
|
||||
if xTouch then
|
||||
if xTouch > xKey and xTouch < xKey + keyWidth then
|
||||
if yTouch > yKey and yTouch < yKey + keyHeight then
|
||||
gfx.rectangle(xKey, yKey, keyWidth, keyHeight, 0, 0xFFFFFFDD)
|
||||
|
||||
local k = alias[key] or key
|
||||
if sticky[k] and layout[sticky[k]] then
|
||||
if currentModifier[1] == sticky[k] and currentModifier[2] == "sticky" then
|
||||
currentModifier = { "default", "sticky" }
|
||||
else
|
||||
currentModifier = { sticky[k], "sticky" }
|
||||
end
|
||||
elseif layout[k] then
|
||||
if currentModifier[1] == k and currentModifier[2] == "normal" then
|
||||
currentModifier = { "default", "sticky" }
|
||||
else
|
||||
currentModifier = { k, "normal" }
|
||||
end
|
||||
else
|
||||
buffer = buffer .. k
|
||||
if currentModifier[1] ~= "default" and currentModifier[2] == "normal" then
|
||||
currentModifier = { "default", "sticky" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
read = function()
|
||||
local ret = buffer
|
||||
buffer = ""
|
||||
|
||||
return ret ~= "" and ret or nil
|
||||
end
|
||||
}
|
||||
|
|
@ -1,64 +1,4 @@
|
|||
local ctr = require("ctr")
|
||||
local gfx = require("ctr.gfx")
|
||||
|
||||
local sel = 1
|
||||
local scroll = 0
|
||||
local curdir = "/"
|
||||
local files = ctr.fs.list(curdir)
|
||||
|
||||
while ctr.run() do
|
||||
ctr.hid.read()
|
||||
local keys = ctr.hid.keys()
|
||||
if keys.down.start then break end
|
||||
|
||||
if keys.down.down and sel < #files then
|
||||
sel = sel + 1
|
||||
if sel > scroll + 14 then scroll = scroll + 1 end
|
||||
elseif keys.down.up and sel > 1 then
|
||||
sel = sel - 1
|
||||
if sel <= scroll then scroll = scroll - 1 end
|
||||
end
|
||||
|
||||
if keys.down.a then
|
||||
local f = files[sel]
|
||||
|
||||
if f.isDirectory then
|
||||
if f.name == ".." then curdir = curdir:gsub("[^/]+/$", "")
|
||||
else curdir = curdir..f.name.."/" end
|
||||
|
||||
sel = 1
|
||||
scroll = 0
|
||||
files = ctr.fs.list(curdir)
|
||||
|
||||
if curdir ~= "/" then
|
||||
table.insert(files, 1, { name = "..", isDirectory = true, fileSize = "parent directory" })
|
||||
end
|
||||
else
|
||||
if f.name:match("%..+$") == ".lua" then
|
||||
dofile(curdir..f.name)
|
||||
-- reset things the script could have changed
|
||||
gfx.set3D(false)
|
||||
gfx.color.setDefault(0xFFFFFFFF)
|
||||
gfx.color.setBackground(0x000000FF)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
gfx.startFrame(gfx.GFX_TOP)
|
||||
|
||||
gfx.rectangle(0, 10+(sel-scroll)*15, gfx.TOP_WIDTH, 15, 0, gfx.color.RGBA8(0, 0, 200))
|
||||
|
||||
for i = scroll+1, scroll+14, 1 do
|
||||
local f = files[i]
|
||||
if not f then break end
|
||||
local name = f.isDirectory and "["..f.name.."]" or f.name.." ("..f.fileSize.."b)"
|
||||
if not f.isHidden then gfx.text(5, 12+(i-scroll)*15, name) end
|
||||
end
|
||||
|
||||
gfx.rectangle(0, 0, gfx.TOP_WIDTH, 25, 0, gfx.color.RGBA8(200, 200, 200))
|
||||
gfx.text(3, 3, curdir, 13, gfx.color.RGBA8(0, 0, 0))
|
||||
|
||||
gfx.endFrame()
|
||||
|
||||
gfx.render()
|
||||
end
|
||||
repeat
|
||||
local file = dofile("sdmc:/3ds/ctruLua/openfile.lua")("Choose a Lua file to execute", "/3ds/ctruLua/", ".lua", "exist")
|
||||
if file then dofile(file) end
|
||||
until not file
|
||||
139
sdcard/3ds/ctruLua/openfile.lua
Normal file
139
sdcard/3ds/ctruLua/openfile.lua
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
--- Open a file explorer to select a file.
|
||||
-- string title: title of the file explorer.
|
||||
-- string curdir: the directory to initially open the file explorer in.
|
||||
-- string exts: the file extensions the user can select, separated by ";". If nil, all extensions are accepted.
|
||||
-- string type: "exist" to select an existing file, "new" to select an non-existing file or "any" to select a existing
|
||||
-- or non-existing file name. If nil, defaults to "exist".
|
||||
-- returns string: the file the user has selected, or nil if the explorer was closed without selecting a file.
|
||||
-- string: "exist" if the file exist or "new" if it doesn't
|
||||
return function(title, curdir, exts, type)
|
||||
-- Open libs
|
||||
local ctr = require("ctr")
|
||||
local gfx = require("ctr.gfx")
|
||||
|
||||
local keyboard = dofile("sdmc:/3ds/ctruLua/keyboard.lua")
|
||||
|
||||
-- Arguments
|
||||
local type = type or "exist"
|
||||
|
||||
-- Variables
|
||||
local sel = 1
|
||||
local scroll = 0
|
||||
local files = ctr.fs.list(curdir)
|
||||
if curdir ~= "/" then table.insert(files, 1, { name = "..", isDirectory = true }) end
|
||||
local newFileName = ""
|
||||
|
||||
local ret = nil
|
||||
|
||||
-- Remember and set defaults
|
||||
--local was3D = gfx.get3D() TODO: implement this thing in ctruLua
|
||||
local wasDefault = gfx.color.getDefault()
|
||||
local wasBackground = gfx.color.getBackground()
|
||||
gfx.set3D(false)
|
||||
gfx.color.setDefault(0xFFFFFFFF)
|
||||
gfx.color.setBackground(0x000000FF)
|
||||
|
||||
while ctr.run() do
|
||||
ctr.hid.read()
|
||||
local keys = ctr.hid.keys()
|
||||
if keys.down.start then break end
|
||||
|
||||
-- Keys input
|
||||
if keys.down.down and sel < #files then
|
||||
sel = sel + 1
|
||||
if sel > scroll + 14 then scroll = scroll + 1 end
|
||||
elseif keys.down.up and sel > 1 then
|
||||
sel = sel - 1
|
||||
if sel <= scroll then scroll = scroll - 1 end
|
||||
end
|
||||
|
||||
if keys.down.a then
|
||||
local f = files[sel]
|
||||
|
||||
if f.isDirectory then
|
||||
if f.name == ".." then curdir = curdir:gsub("[^/]+/$", "")
|
||||
else curdir = curdir..f.name.."/" end
|
||||
|
||||
sel = 1
|
||||
scroll = 0
|
||||
files = ctr.fs.list(curdir)
|
||||
|
||||
if curdir ~= "/" then
|
||||
table.insert(files, 1, { name = "..", isDirectory = true })
|
||||
end
|
||||
elseif type == "exist" or type == "any" then
|
||||
if exts then
|
||||
for ext in (exts..";"):gmatch("[^;]+") do
|
||||
if f.name:match("%..+$") == ext then
|
||||
ret = { curdir..f.name, "exist" }
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
ret = { curdir..f.name, "exist" }
|
||||
end
|
||||
if ret then break end
|
||||
end
|
||||
end
|
||||
|
||||
-- Keyboard input
|
||||
if type == "new" or type == "any" then
|
||||
local input = keyboard.read()
|
||||
if input then
|
||||
if input == "BACK" then
|
||||
newFileName = newFileName:sub(1, (utf8.offset(newFileName, -1) or 0)-1)
|
||||
elseif input == "\n" then
|
||||
local fileStatus = "new"
|
||||
local f = io.open(curdir..newFileName)
|
||||
if f then fileStatus = "exist" f:close() end
|
||||
ret = { curdir..newFileName, fileStatus }
|
||||
break
|
||||
else
|
||||
newFileName = newFileName..input
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw
|
||||
gfx.startFrame(gfx.GFX_TOP)
|
||||
|
||||
gfx.rectangle(0, 10+(sel-scroll)*15, gfx.TOP_WIDTH, 15, 0, gfx.color.RGBA8(0, 0, 200))
|
||||
|
||||
for i = scroll+1, scroll+14, 1 do
|
||||
local f = files[i]
|
||||
if not f then break end
|
||||
local name = f.isDirectory and "["..f.name.."]" or f.name.." ("..f.fileSize.."b)"
|
||||
if not f.isHidden then gfx.text(5, 12+(i-scroll)*15, name) end
|
||||
end
|
||||
|
||||
gfx.rectangle(0, 0, gfx.TOP_WIDTH, 25, 0, gfx.color.RGBA8(200, 200, 200))
|
||||
gfx.text(3, 3, curdir, 13, gfx.color.RGBA8(0, 0, 0))
|
||||
|
||||
gfx.endFrame()
|
||||
|
||||
gfx.startFrame(gfx.GFX_BOTTOM)
|
||||
|
||||
gfx.text(5, 5, title)
|
||||
gfx.text(5, 20, "Accepted file extensions: "..(exts or "all"))
|
||||
|
||||
if type == "new" or type == "any" then
|
||||
gfx.text(5, 90, newFileName)
|
||||
keyboard.draw(5, 115)
|
||||
end
|
||||
|
||||
gfx.endFrame()
|
||||
|
||||
gfx.render()
|
||||
end
|
||||
|
||||
-- Reset defaults
|
||||
--gfx.set3D(was3D)
|
||||
gfx.color.setDefault(wasDefault)
|
||||
gfx.color.setBackground(wasBackground)
|
||||
|
||||
if ret then
|
||||
return table.unpack(ret)
|
||||
else
|
||||
return ret
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue