Realtime Fullscreen GLSL Editor

#UPDATE: Please go to LiveCodeEditor
(this post is too messy)

Hi everyone!

I just wanted to share my LIVE code editor with all of you.

Features:
- Custom cursor shape and color
- Cursor navigation using hardware keyboard
- Cursor navigation using finger
- Multiple lines

To do:
~~- Fix misaligned cursor / cursor spacing in certain font sizes ~~
~~- Text scrolling action ~~
~~- Syntax highlighting ~~
~~- Custom text wrapper ~~
~~- Save document changes ~~
~~- Load document ~~
~~- Create new document ~~
~~- Multiple document in tabs ~~

I hope this is useful!

(edit: screenshot added)

-- RealtimeEditor by @marcelliino @invinixity

function setup()
    displayMode(STANDARD)
    editor = TextEditor()
    editor:input(fragment())
    editor.monkey = false
    editor.fontSize = 16
    editor.l_m = 8
    editor.t_m = 8
    parameter.text("OUTPUT")
    parameter.watch("MONITOR")
    parameter.watch("CURSOR_NAVIGATION")
    CURSOR_NAVIGATION =
    "STEP"..
    "\

(option + Y) \u{2191} UP"…
"
(option + H) \u{2193} DOWN"…
"
(option + G) \u{2190} LEFT"…
"
(option + J) \u{2192} RIGHT"…
"

TELEPORT"…
"
(shift + option + Y) \u{2912} START"…
"
(shift + option + H) \u{2913} END"…
"
(shift + option + G) \u{21e4} HEAD"…
"
(shift + option + J) \u{21e5} TAIL"
end

function draw()
    background(255)
    
    D = vec2(WIDTH, HEIGHT)
    T = CurrentTouch.pos
    
    myMesh = mesh()
    myShader = shader()
    
    myShader.vertexProgram = vertex()
    myShader.fragmentProgram = TextEditor:output()
    myShader.resolution = D
    myShader.touch = T
    myShader.time = ElapsedTime
    
    myMesh.shader = myShader
    
    myMesh.vertices = {
        vec2(0*D.x, 0*D.y),
        vec2(0*D.x, 1*D.y),
        vec2(1*D.x, 1*D.y),
        vec2(1*D.x, 1*D.y),
        vec2(1*D.x, 0*D.y),
        vec2(0*D.x, 0*D.y)
    }
    
    myMesh.texCoords = {
        vec2(0, 0),
        vec2(0, 1),
        vec2(1, 1),
        vec2(1, 1),
        vec2(1, 0),
        vec2(0, 0)
    }
    
    myMesh:setColors(255,255,255,255)
    myMesh:draw()
    
    editor:draw()
    OUTPUT = editor:output()
    MONITOR = editor.details
end

function touched(touch)
    editor:touched(touch)
end

function keyboard(key)
    editor:keyboard(key)
end
---------- SHADER CODES ----------

function vertex()
    return
[[
uniform mat4 modelViewProjection;
 
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
 
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
 
void main(){
    vColor = color;
    vTexCoord = texCoord;
    
    gl_Position = modelViewProjection * position;
}
]]
end

function fragment()
    return
[[
precision highp float;
 
uniform vec2 resolution;
uniform vec2 touch;
uniform float time;
 
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
 
#define nsin(n) sin(n)*0.5+0.5
#define ncos(n) cos(n)*0.5+0.5
#define ntan(n) tan(n)*0.5+0.5
 
vec3 liquid(vec2 position, float mult, vec2 time){
    float len;
    for(float i = 0.0; i < mult; i++){
        len = length(position);
        position.x = position.x + sin(position.y + cos(len-pow(ncos(len), mult*0.5))) + cos(time.x);
        position.y = position.y - cos(position.x + sin(len-pow(nsin(len), mult*0.5))) + sin(time.y);
    }
    return vec3(position, len);
}

void main(){
    vec2 uv = vTexCoord * 2.0 - 1.0;
    #define res resolution
    vec2 mouse = (touch/res) * 2.0 - 1.0;
    float ratio = max(res.x, res.y)/min(res.x, res.y);
    
    if(res.x > res.y){
        uv.x *= ratio;
        mouse.x *= ratio;
    }else{
        uv.y *= ratio;
        mouse.y *= ratio;
    }
    
    vec4 col = vec4(1.0);
    
    vec3 l = liquid(uv*2.0, 3.0, vec2(time*0.9, time*1.2));
    col.rgb = ntan(l);
    
    gl_FragColor = vec4(col.rgb, 1.0) * vColor;
}
]]
end
---------- TEXT EDITOR CLASS ----------

TextEditor = class()

local editor = {""}
local lines, cursorPos, nW = 1, 1, 1
local inline, tempCursorPos = 0, 0
local head, tail = "", ""

function TextEditor:init()
    
    font("Inconsolata")
    textMode(CORNER)
    
    self.fontSize = 20
    self.t_m = 10
    self.b_m = 10
    self.l_m = 10
    self.r_m = 20
    
    self.monkey = false
    
    self:updateinfo()
    
    showKeyboard()
end

function TextEditor:updateinfo()
    self.details = "current line: "..lines+inline.." / "..lines..
    "\

cursor position: “…cursorPos…” / "…#editor[lines+inline]…
"
preserved position: "…tempCursorPos
end

function TextEditor:draw()
    
    fontSize(self.fontSize)
    
    nW = textSize(lines)
    for n, t in pairs(editor) do
        local tW, tH = textSize(t)
        local h = self.t_m + tH * n
        
        fill(50, 205)
        pushStyle()
        textMode(CENTER)
        text(n, self.l_m+nW/2+1.25, HEIGHT-h+tH/2-1.25)
        fill(255, 180)
        text(n, self.l_m+nW/2, HEIGHT-h+tH/2)
        popStyle()
        
        if n ~= lines+inline then
            rect(self.l_m+nW+5, HEIGHT-h-0.505, tW+10, tH+1.05)
            fill(25, 130)
            text(t, self.l_m+nW+10+1.25, HEIGHT-h-1.25)
            fill(255)
            text(t, self.l_m+nW+10, HEIGHT-h)
        end
        
    end
    
    local currentLine = editor[lines+inline]
    
    local blink = math.sin(ElapsedTime/0.125)*0.5+1.0
    local cursor = "" --string.rep("\\u{25ae}", math.floor(blink))..string.rep("\\u{25af}", 1-math.floor(blink))
    if self.monkey then cursor = string.rep(" ", math.floor(blink))..string.rep(" ", 1-math.floor(blink)) end
    
    head = string.sub(currentLine, 1, cursorPos)
    tail = string.sub(currentLine, cursorPos+1, #currentLine)
    
    local editorW, editorH = textSize(currentLine)
    local h = self.t_m + editorH * (lines+inline)
    fill(50 , 140+60*blink)
    rect(-5, HEIGHT-h, self.l_m+2.5, editorH)
    rect(self.l_m+nW+5, HEIGHT-h, WIDTH+10, editorH)
    fill(0)
    text(head..cursor..tail, self.l_m+nW+10+1.25, HEIGHT-h-1.25)
    fill(255)
    text(head..cursor..tail, self.l_m+nW+10, HEIGHT-h)
    
    local sW, sH = textSize(" ")
    local cP = vec2(self.l_m+nW+10+sW*cursorPos, HEIGHT-self.t_m-sH*(lines+inline))
    pushStyle()
    stroke(255)
    strokeWidth(3.75*blink)
    lineCapMode(PROJECT)
    if self.monkey == false then line(cP.x, cP.y+2.5, cP.x, cP.y+self.fontSize-2.5) end
    popStyle()
    
end

function TextEditor:input(s)
    lines = 0
    local t = {}
    for l in string.gmatch(s, "([^".."\

“…”]+)") do
table.insert(t, l)
lines = lines + 1
end
editor = t
cursorPos = #editor[lines]
tempCursorPos = 0
self:updateinfo()
end

function TextEditor:output()
    return table.concat(editor, "\

")
end

function TextEditor:touched(touch)
    local cL = editor[lines+inline]
    lW, lH = textSize(cL)
    
    inline = 1-lines+math.floor((HEIGHT-self.t_m-touch.y)/lH)
    if inline >= 0 then inline = 0
    elseif 1-inline >= lines then inline = 1-lines
    end
    
    cursorPos = math.floor((touch.x-self.l_m-nW-5)/lW*#cL)
    if #cL == 0 then cursorPos = 0
    elseif cursorPos <= 0 then cursorPos = 0
    elseif cursorPos >= #cL then cursorPos = #cL
    end
    tempCursorPos = cursorPos
    
    self:updateinfo()
end

function TextEditor:keyboard(key)
    
    -- NEW LINE --
    if key == RETURN then
        editor[lines+inline] = head
        lines = lines + 1
        cursorPos = 0
        tempCursorPos = cursorPos
        table.insert(editor, lines+inline, tail)
    -- DELETE --
    elseif key == BACKSPACE then
        if #head > 0 then
            editor[lines+inline] = string.sub(head, 1, -2)..tail
            cursorPos = cursorPos - 1
        elseif 1-inline < lines then
            lines = lines - 1
            cursorPos = #editor[lines+inline]
            editor[lines+inline] = editor[lines+inline]..tail
            table.remove(editor, lines+inline+1)
        end
        tempCursorPos = cursorPos
    -- STEP UP --
    elseif key == '¥' then
        if inline == 0
        and cursorPos == #editor[lines]
        and tempCursorPos ~= #editor[lines]
        then
            if #editor[lines+inline] < tempCursorPos then
                inline = inline-1
            end
            cursorPos = tempCursorPos
        elseif 1-inline < lines then
            if #editor[lines+inline-1] >= tempCursorPos then
                cursorPos = tempCursorPos
            else
                cursorPos = #editor[lines+inline-1]
            end
            inline = inline - 1
        else
            cursorPos = 0
        end
    -- STEP DOWN --
    elseif key == '?' then
        if 1-inline == lines
        and cursorPos == 0
        and tempCursorPos ~= 0
        then
            if #editor[lines+inline] > tempCursorPos then
                cursorPos = tempCursorPos
            else
                cursorPos = #editor[lines+inline]
            end
        elseif inline < 0 then
            if #editor[lines+inline+1] >= tempCursorPos then
                cursorPos = tempCursorPos
            else
                cursorPos = #editor[lines+inline+1]
            end
            inline = inline + 1
        else
            cursorPos = #editor[lines+inline]
        end
    -- STEP LEFT --
    elseif key == '©' then
        if cursorPos > 0 then
            cursorPos = cursorPos - 1
        elseif 1-inline < lines then
            inline = inline - 1
            cursorPos = #editor[lines+inline]
        end
        tempCursorPos = cursorPos
    -- STEP RIGHT --
    elseif key == '?' then
        if cursorPos < #editor[lines+inline] then
            cursorPos = cursorPos + 1
        elseif inline < 0 then
            inline = inline + 1
            cursorPos = 0
        end
        tempCursorPos = cursorPos
    -- TELEPORT TO START --
    elseif key == 'Á' then
        inline = 1-lines  
        cursorPos = 0
    -- TELEPORT TO END --
    elseif key == 'Ó' then
        inline = 0
        cursorPos = #editor[lines]
    -- JUMP LEFT --
    elseif key == '?' then
        cursorPos = 0
        tempCursorPos = cursorPos
    -- JUMP RIGHT --
    elseif key == 'Ô' then
        cursorPos = #editor[lines+inline]
        tempCursorPos = cursorPos
    -- TAB REPLACEMENT --
    elseif key == string.match(key, "%c") then
        editor[lines+inline] = head.."  "..tail
        cursorPos = cursorPos + 2
        tempCursorPos = cursorPos
    -- OTHER KEYS --
    elseif key == string.match(key, ".") then
        editor[lines+inline] = head..key..tail
        cursorPos = cursorPos + 1
        tempCursorPos = cursorPos
    end
    self:updateinfo()
    
end

@marcelliino If you’re done adding the code above, then you’re missing code at the end in the section function TextEditor:draw(). I loaded the zip file and that works OK. Haven’t tried anything other than just running it.

@dave1707 yes, I’ve edited it :wink:
It was because the monkey emoji :confused:

@marcelliino Emojis don’t show very well when displayed in the code in the forum. You’re missing a lot of code in the TextEditor:draw() function in the code you show above. Apparently when an emoji is encountered, none of the code after it is shown. An if statement is started, but not finished.

@dave1707 yep, sorry for that. Thanks for the advice :smiley: