local BASE = (...):gsub('input', '') local Widget = require(BASE..'widget') local core = require(BASE..'core') local utf8 = require 'utf8' -- Grabs keyboard on focus local Input = setmetatable({ grabkeyboard = true }, Widget) Input.__index = Input local function split(str, pos) local ofs = utf8.offset(str, pos) or 0 return str:sub(1, ofs-1), str:sub(ofs) end function Input.new(args) local self = setmetatable(args, Input) self.text = self.text or "" self.color = self.color or core.theme.color self.cornerRadius = self.cornerRadius or core.theme.cornerRadius self.cursor = math.max(1, math.min(utf8.len(self.text)+1, self.cursor or utf8.len(self.text)+1)) self.candidate = { text = "", start = 0, length = 0 } return self end function Input:onPointerInput(px,py, clicked) if clicked then self:grabFocus() -- Schedule cursor reposition for next draw() self.px = px self.py = py end end function Input:textedited(text, start, length) self.candidate.text = text self.candidate.start = start self.candidate.length = length end function Input:textinput(text) if text ~= "" then local a,b = split(self.text, self.cursor) self.text = table.concat {a, text, b} self.cursor = self.cursor + utf8.len(text) end end function Input:keypressed(key, code, isrepeat) if isrepeat == nil then -- LOVE sends 3 types of keypressed() events, -- 1. with isrepeat = true -- 2. with isrepeat = false -- 3. with isrepeat = nil -- -- We're only interested in the first 2. return end if self.candidate.length == 0 then if key == 'backspace' then local a,b = split(self.text, self.cursor) self.text = table.concat { split(a, utf8.len(a)), b } self.cursor = math.max(1, self.cursor-1) elseif key == 'delete' then local a,b = split(self.text, self.cursor) local _,b = split(b, 2) self.text = table.concat { a, b } elseif key == 'left' then self.cursor = math.max(0, self.cursor-1) elseif key == 'right' then self.cursor = math.min(utf8.len(self.text)+1, self.cursor+1) end end end function Input:keyreleased(key) if self.candidate.length == 0 then if key == 'home' then self.cursor = 1 elseif key == 'end' then self.cursor = utf8.len(self.text)+1 elseif key == 'return' then self:onChange(self.text) self.cursor = 1 end end end function Input:draw() -- Cursor position is before the char (including EOS) i.e. in "hello": -- position 1: |hello -- position 2: h|ello -- ... -- position 6: hello| local x,y,w,h = self.x,self.y,self.w,self.h local font = self.font or love.graphics.getFont() local th = font:getHeight() local tw = font:getWidth(self.text) -- Get size of text and cursor position local cursor_pos = 0 if self.cursor > 1 then local s = self.text:sub(1, utf8.offset(self.text, self.cursor)-1) cursor_pos = font:getWidth(s) end -- Compute drawing offset if self.px ~= nil then -- Mouse movement local mx = self.px - self.x self.cursor = utf8.len(self.text) + 1 for c = 1,self.cursor do local s = self.text:sub(0, utf8.offset(self.text, c)-1) if font:getWidth(s) >= mx then self.cursor = c-1 break end end self.px,self.py = nil,nil end core.drawBox(x,y,w,h, self.color.normal, self.cornerRadius) -- Apply text margins w = math.max(w - 6, 0) x = math.min(x + 3, x + w) -- Set scissors local sx, sy, sw, sh = love.graphics.getScissor() love.graphics.setScissor(x-1,y,w+2,h) -- Text love.graphics.setColor(self.color.normal.fg) love.graphics.setFont(font) love.graphics.print(self.text, x, y + (h-th)/2) if self.candidate.length > 0 then -- Candidate text local ctw = font:getWidth(self.candidate.text) love.graphics.setColor(self.color.normal.fg) love.graphics.print(self.candidate.text, x + tw, y + (h-th)/2) -- Candidate text rectangle box love.graphics.rectangle('line', x + tw, y + (h-th)/2, ctw, th) self.candidate.text = "" self.candidate.start = 0 self.candidate.length = 0 end -- Cursor if self:isFocused() and (love.timer.getTime() % 1) > .5 then local ct = self.candidate local ss = ct.text:sub(1, utf8.offset(ct.text, ct.start)) local ws = ct.start > 0 and font:getWidth(ss) or 0 love.graphics.setLineWidth(1) love.graphics.setLineStyle('rough') love.graphics.line(x + cursor_pos + ws, y + (h-th)/2, x + cursor_pos + ws, y + (h+th)/2) end -- Reset scissor love.graphics.setScissor(sx,sy,sw,sh) end return Input