Files
japanese-srs-trainer-wanikani/awesome/src/modules/inputbox/new.lua

486 lines
15 KiB
Lua

local Pango = require('lgi').Pango
local PangoCairo = require('lgi').PangoCairo
local abutton = require('awful.button')
local akey = require('awful.key')
local akeygrabber = require('awful.keygrabber')
local aplacement = require('awful.placement')
local apopup = require('awful.popup')
local base = require('wibox.widget.base')
local beautiful = require('beautiful')
local cairo = require('lgi').cairo
local gobject = require('gears.object')
local gstring = require('gears.string')
local gsurface = require('gears.surface')
local gtable = require('gears.table')
local gtimer = require('gears.timer')
local imagebox = require('wibox.widget.imagebox')
local setmetatable = setmetatable
local textbox = require('wibox.widget.textbox')
local wibox = require('wibox')
local dpi = beautiful.xresources.apply_dpi
local capi = {
selection = selection,
mousegrabber = mousegrabber,
mouse = mouse,
}
local inputbox = {}
local function get_text_extent(text, font, font_size, args)
local surface = cairo.ImageSurface(cairo.Format.ARGB32, 0, 0)
local cr = cairo.Context(surface)
cr:select_font_face(font, args)
cr:set_font_size(font_size)
return cr:text_extents(text)
end
function inputbox.draw_text(self)
local text = self:get_text()
local highlight = self:get_highlight()
local fg_color = { 1, 1, 1 }
local cursor_color = { 1, 1, 1, 1 }
if text == '' then
fg_color = { 0.2, 0.2, 0.2 }
-- Silently change the text, it will be changed back after it is drawn
self._private.layout:set_text(self._private.text_hint)
end
local _, pango_extent = self._private.layout:get_extents()
local surface = cairo.ImageSurface(cairo.Format.ARGB32, (pango_extent.width / Pango.SCALE) + pango_extent.x, (pango_extent.height / Pango.SCALE) + pango_extent.y)
local cr = cairo.Context(surface)
-- Draw highlight
if (highlight.start_pos ~= 0) or (highlight.end_pos ~= 0) then
cr:set_source_rgb(0, 0, 1)
local txt = text:sub(self:get_highlight().start_pos + 1, self:get_highlight().end_pos)
cr:rectangle(
cr:text_extents(text:sub(0, self:get_highlight().start_pos)).x_advance,
pango_extent.y / Pango.SCALE,
cr:text_extents(txt).width,
pango_extent.height / Pango.SCALE
)
cr:fill()
end
-- Draw text
PangoCairo.update_layout(cr, self._private.layout)
cr:set_source_rgba(1, 1, 1, 1)
cr:move_to(0, 0)
PangoCairo.show_layout(cr, self._private.layout)
-- Draw cursor
cr:set_source_rgba(table.unpack(cursor_color))
local cursor = self:get_cursor_pos()
cr:rectangle(
cursor.x / Pango.SCALE,
cursor.y / Pango.SCALE,
2,
cursor.height / Pango.SCALE)
cr:fill()
self.widget:set_image(surface)
return surface
end
function inputbox:start_keygrabber()
self.akeygrabber = akeygrabber {
autostart = true,
stop_key = { 'Escape', 'Return' },
start_callback = function()
end,
stop_callback = function()
end,
keybindings = {
akey {
modifiers = {},
key = 'BackSpace',
on_press = function()
local hl = self:get_highlight()
local text = self:get_text()
local cursor_pos = self:get_cursor_index()
if hl.end_pos ~= hl.start_pos then
self:set_text(text:sub(0, hl.start_pos) .. text:sub(hl.end_pos + 1, #text))
self:set_cursor_pos(hl.start_pos)
self:set_highlight { start_pos = 0, end_pos = 0 }
else
self:set_text(text:sub(1, cursor_pos - 1) .. text:sub(cursor_pos + 1))
self:set_cursor_pos(cursor_pos - 1)
end
end,
},
akey {
modifiers = {},
key = 'Delete',
on_press = function()
local hl = self:get_highlight()
local text = self:get_text()
local cursor_pos = self:get_cursor_index()
if hl.end_pos ~= hl.start_pos then
self:set_text(text:sub(0, hl.start_pos) .. text:sub(hl.end_pos + 1, #text))
self:set_cursor_pos(hl.start_pos)
self:set_highlight { start_pos = 0, end_pos = 0 }
else
self:set_text(text:sub(1, cursor_pos) .. text:sub(cursor_pos + 2, #text))
end
end,
},
akey {
modifiers = {},
key = 'Left',
on_press = function()
self:set_cursor_pos(self:get_cursor_index() - 1)
self:set_highlight { start_pos = 0, end_pos = 0 }
end,
},
akey {
modifiers = {},
key = 'Right',
on_press = function()
self:set_cursor_pos(self:get_cursor_index() + 1)
self:set_highlight { start_pos = 0, end_pos = 0 }
end,
},
akey {
modifiers = {},
key = 'Home',
on_press = function()
self:set_cursor_pos(0)
self:set_highlight { start_pos = 0, end_pos = 0 }
end,
},
akey {
modifiers = {},
key = 'End',
on_press = function()
self:set_cursor_pos(#self:get_text())
self:set_highlight { start_pos = 0, end_pos = 0 }
end,
},
akey {
modifiers = { 'Shift' },
key = 'Left',
on_press = function()
local cursor_pos = self:get_cursor_pos()
local hl = self:get_highlight()
if cursor_pos == hl.start_pos then
self:set_cursor_pos(cursor_pos - 1)
self:set_highlight { start_pos = self:get_cursor_pos(), end_pos = hl.end_pos }
elseif cursor_pos == hl.end_pos then
self:set_cursor_pos(cursor_pos - 1)
self:set_highlight { start_pos = hl.start_pos, end_pos = self:get_cursor_pos() }
else
if (hl.start_pos ~= cursor_pos) and (hl.end_pos ~= cursor_pos) then
self:set_highlight { start_pos = cursor_pos, end_pos = cursor_pos }
hl = self:get_highlight()
self:set_cursor_pos(cursor_pos - 1)
self:set_highlight { start_pos = self:get_cursor_pos(), end_pos = hl.end_pos }
end
end
end,
},
akey {
modifiers = { 'Shift' },
key = 'Right',
on_press = function()
local cursor_pos = self:get_cursor_pos()
local hl = self:get_highlight()
if cursor_pos == hl.end_pos then
self:set_cursor_pos(cursor_pos + 1)
self:set_highlight { start_pos = hl.start_pos, end_pos = self:get_cursor_pos() }
elseif cursor_pos == hl.start_pos then
self:set_cursor_pos(cursor_pos + 1)
self:set_highlight { start_pos = self:get_cursor_pos(), end_pos = hl.end_pos }
else
if (hl.start_pos ~= cursor_pos) and (hl.end_pos ~= cursor_pos) then
self:set_highlight { start_pos = cursor_pos, end_pos = cursor_pos }
hl = self:get_highlight()
self:set_cursor_pos(cursor_pos + 1)
self:set_highlight { start_pos = hl.start_pos, end_pos = self:get_cursor_pos() }
end
end
end,
},
akey {
modifiers = { 'Control' },
key = 'a',
on_press = function()
self:set_highlight { start_pos = 0, end_pos = #self:get_text() }
self:set_cursor_pos(#self:get_text() - 1)
end,
},
akey {
modifiers = { 'Control' },
key = 'c',
on_press = function()
local hl = self:get_highlight()
if hl.start_pos ~= hl.end_pos then
local text = self:get_text():sub(hl.start_pos, hl.end_pos)
--TODO:self:copy_to_clipboard(text)
end
end,
},
akey {
modifiers = { 'Control' },
key = 'v',
on_press = function()
local hl = self:get_highlight()
local selection = capi.selection()
if hl.start_pos ~= hl.end_pos then
self:set_text(self:get_text():sub(1, hl.start_pos) .. selection .. self:get_text():sub(hl.end_pos + 1, #self:get_text()))
self:set_cursor_pos(hl.start_pos + #selection)
self:set_highlight { start_pos = 0, end_pos = 0 }
else
self:set_text(self:get_text():sub(1, self:get_cursor_pos()) .. selection .. self:get_text():sub(self:get_cursor_pos() + 1, #self:get_text()))
self:set_cursor_pos(self:get_cursor_pos() + #selection)
end
end,
},
akey {
modifiers = { 'Control' },
key = 'x',
on_press = function()
--TODO
end,
},
akey {
modifiers = { 'Control' },
key = 'Right',
on_press = function()
end,
},
akey {
modifiers = { 'Control' },
key = 'Left',
on_press = function()
end,
},
},
keypressed_callback = function(_, mod, key)
local text = self:get_text()
local cursor_pos = self:get_cursor_index()
if (mod[1] == 'Mod2' or '') and (key:wlen() == 1) then
self:set_text(text:sub(1, cursor_pos) .. key .. text:sub(cursor_pos + 1, #text))
self:set_cursor_pos(cursor_pos + #key)
elseif (mod[1] == 'Shift') and (key:wlen() == 1) then
self:set_text(text:sub(1, cursor_pos) .. key:upper() .. text:sub(cursor_pos + 1, #text))
self:set_cursor_pos(cursor_pos + #key)
end
end,
}
end
--[[ function inputbox:advanced_by_character(mx, last_x)
local delta = mx - last_x
local character
local text = self:get_text()
local cursor_pos = self:get_cursor_pos()
if delta < 0 then
character = text:sub(cursor_pos + 1, cursor_pos + 1)
else
character = text:sub(cursor_pos - 1, cursor_pos - 1)
end
if character then
local extents = get_text_extent(character, self.font, self.font_size, { cairo.FontSlant.NORMAL, cairo.FontWeight.NORMAL })
if math.abs(delta) >= extents.x_advance then
self:set_cursor_pos(cursor_pos + (((delta > 0) and 1) or -1))
last_x = mx
return (((delta > 0) and 1) or -1), last_x
end
end
return 0, last_x
end ]]
function inputbox:start_mousegrabber(x, y)
--[[ if not mousegrabber.isrunning() then
local last_x, advanced, cursor_pos, mx = x, nil, self:get_cursor_pos(), nil
local hl = self:get_highlight()
self:set_highlight { start_pos = cursor_pos, end_pos = cursor_pos }
local text = self:get_text()
mousegrabber.run(function(m)
mx = m.x
if (math.abs(mx - x) > 5) then
advanced, last_x = self:advanced_by_character(mx, last_x)
if advanced == 1 then
if cursor_pos <= hl.start_pos then
if hl.end_pos < #text then
self:set_highlight { start_pos = hl.start_pos, end_pos = hl.end_pos + 1 }
end
else
self:set_highlight { start_pos = hl.start_pos + 1, end_pos = hl.end_pos }
end
elseif advanced == -1 then
if cursor_pos >= hl.end_pos then
if hl.start_pos > 1 then
self:set_highlight { start_pos = hl.start_pos - 1, end_pos = hl.end_pos }
end
else
self:set_highlight { start_pos = hl.start_pos, end_pos = hl.end_pos - 1 }
end
end
end
hl = self:get_highlight()
return m.buttons[1]
end, 'xterm')
end ]]
if not mousegrabber.isrunning() then
local index, _ = self._private.layout:xy_to_index(x * Pango.SCALE, y * Pango.SCALE)
if not index then return end
self:set_cursor_pos(index)
-- Remove highlight, but also prepare its position (same pos = no highlight)
self:set_highlight { start_pos = index, end_pos = index }
local text = self:get_text()
local cursor_pos = self:get_cursor_pos()
local hl = self:get_highlight()
mousegrabber.run(function(m)
index, _ = self._private.layout:xy_to_index(m.x * Pango.SCALE, m.y * Pango.SCALE)
if not index then return end
if math.abs(index - cursor_pos) == 1 then
if cursor_pos <= hl.start_pos then
if hl.end_pos < #text then
self:set_highlight { start_pos = hl.start_pos, end_pos = hl.end_pos + 1 }
end
else
self:set_highlight { start_pos = hl.start_pos + 1, end_pos = hl.end_pos }
end
elseif math.abs(index - cursor_pos) == -1 then
if cursor_pos >= hl.end_pos then
if hl.start_pos > 1 then
self:set_highlight { start_pos = hl.start_pos - 1, end_pos = hl.end_pos }
end
else
self:set_highlight { start_pos = hl.start_pos, end_pos = hl.end_pos - 1 }
end
end
if index ~= cursor_pos then
self:set_cursor_pos(index)
end
return m.buttons[1]
end, 'xterm')
end
end
function inputbox:set_cursor_pos_from_mouse(x, y)
-- When setting the cursor position, trailing is not needed as its handled by the setter
local index, _ = self._private.layout:xy_to_index(x * Pango.SCALE, y * Pango.SCALE)
if not index then return end
self:set_highlight { start_pos = 0, end_pos = 0 }
self:set_cursor_pos(index)
end
function inputbox:get_text()
return self._private.layout:get_text()
end
function inputbox:set_text(text)
if self:get_text() == text then return end
local attributes, parsed = Pango.parse_markup(text, -1, 0)
if not attributes then return parsed.message or tostring(parsed) end
self._private.layout:set_text(parsed, string.len(parsed))
self._private.layout:set_attributes(attributes)
self.draw_text(self)
end
function inputbox:get_cursor_pos()
return self._private.layout:get_cursor_pos(self._private.cursor_pos.index)
end
function inputbox:get_cursor_index()
return self._private.cursor_pos.index
end
function inputbox:set_cursor_pos(cursor_pos)
-- moving only moved one character set, to move it multiple times we need to loop as long as the difference to the new cursor isn't 0
if not cursor_pos or (cursor_pos < 0) or (cursor_pos >= #self:get_text()) then return end
while (cursor_pos - self._private.cursor_pos.index) ~= 0 do
self._private.cursor_pos.index, self._private.cursor_pos.trailing = self._private.layout:move_cursor_visually(
true, self._private.cursor_pos.index,
self._private.cursor_pos.trailing,
cursor_pos - self._private.cursor_pos.index
)
end
self.draw_text(self)
end
function inputbox:get_highlight()
return self._private.highlight
end
function inputbox:set_highlight(highlight)
self._private.highlight = highlight
self.draw_text(self)
end
function inputbox:focus()
end
function inputbox:unfocus()
end
function inputbox.new(args)
local ret = gobject { enable_properties = true }
args.text = args.text .. '\n'
gtable.crush(ret, inputbox)
ret._private = {}
ret._private.context = PangoCairo.font_map_get_default():create_context()
ret._private.layout = Pango.Layout.new(ret._private.context)
ret.font_size = 24
ret.font = 'JetBrainsMono Nerd Font, ' .. 24
ret._private.layout:set_font_description(Pango.FontDescription.from_string('JetBrainsMono Nerd Font 16'))
ret._private.text_hint = args.text_hint or ''
ret._private.cursor_pos = {
index = args.cursor_pos or 0,
trailing = 0,
}
ret._private.highlight = args.highlight or {
start_pos = 0,
end_pos = 0,
}
ret.widget = imagebox(nil, false)
ret.widget:connect_signal('button::press', function(_, x, y, button)
if button == 1 then
ret:set_cursor_pos_from_mouse(x, y)
--ret:start_mousegrabber(x, y)
ret:start_keygrabber()
end
end)
ret:set_text(args.text or '')
--ret:set_cursor_pos(ret._private.cursor_pos)
ret:set_highlight(ret._private.highlight)
return ret
end
return setmetatable(inputbox, {
__call = function(_, ...)
return inputbox.new(...)
end,
})