aboutsummaryrefslogtreecommitdiff
path: root/.config/awesome/ui/fresnel/text_input.lua
diff options
context:
space:
mode:
authordelta <darkussdelta@gmail.com>2026-01-19 06:30:33 +0100
committerdelta <darkussdelta@gmail.com>2026-01-19 06:30:33 +0100
commit225eeafcea67d63a608f9c666faf2a2ef014be4a (patch)
tree7bc59e81983a02d44085545a385058a35de0a8ef /.config/awesome/ui/fresnel/text_input.lua
parentd7c66522cf365f516babcfeb1d4a2d36c3ea41af (diff)
sync: electric boogaloo
Diffstat (limited to '.config/awesome/ui/fresnel/text_input.lua')
-rw-r--r--.config/awesome/ui/fresnel/text_input.lua934
1 files changed, 934 insertions, 0 deletions
diff --git a/.config/awesome/ui/fresnel/text_input.lua b/.config/awesome/ui/fresnel/text_input.lua
new file mode 100644
index 0000000..2dc7368
--- /dev/null
+++ b/.config/awesome/ui/fresnel/text_input.lua
@@ -0,0 +1,934 @@
+-------------------------------------------
+-- @author https://github.com/Kasper24
+-- @copyright 2021-2025 Kasper24
+-------------------------------------------
+local lgi = require('lgi')
+local Gtk = lgi.require('Gtk', '3.0')
+local Gdk = lgi.require('Gdk', '3.0')
+local Pango = lgi.Pango
+local awful = require("awful")
+local gtable = require("gears.table")
+local gtimer = require("gears.timer")
+local gcolor = require("gears.color")
+local wibox = require("wibox")
+local qcolor = require "quarrel.color"
+local abs = math.abs
+local ipairs = ipairs
+local string = string
+local capi = {
+ awesome = awesome,
+ root = root,
+ tag = tag,
+ client = client,
+ mouse = mouse,
+ mousegrabber = mousegrabber
+}
+
+local text_input = {
+ mt = {}
+}
+
+local properties = {
+ "unfocus_keys",
+ "unfocus_on_root_clicked", "unfocus_on_client_clicked", "unfocus_on_client_focus",
+ "unfocus_on_mouse_leave", "unfocus_on_tag_change",
+ "focus_on_subject_mouse_enter", "unfocus_on_subject_mouse_leave",
+ "click_timeout",
+ "reset_on_unfocus",
+ "text_color",
+ "placeholder", "initial",
+ "pattern", "obscure",
+ "cursor_blink", "cursor_blink_rate","cursor_size", "cursor_bg",
+ "selection_bg"
+}
+
+text_input.patterns = {
+ numbers = "[%d.]*",
+ numbers_one_decimal = "%d*%.?%d*",
+ round_numbers = "[0-9]*",
+ email = "%S+@%S+%.%S+",
+ time = "%d%d?:%d%d:%d%d?|%d%d?:%d%d",
+ date = "%d%d%d%d%-%d%d%-%d%d|%d%d?/%d%d?/%d%d%d%d|%d%d?%.%d%d?%.%d%d%d%d",
+ phone = "%+?%d[%d%-%s]+%d",
+ url = "https?://[%w-_%.]+%.[%w]+/?[%w-_%.?=%+]*",
+ email = "[%w._%-%+]+@[%w._%-]+%.%w+",
+ alphanumeric = "%w+",
+ letters = "[a-zA-Z]+"
+}
+
+local function build_properties(prototype, prop_names)
+ for _, prop in ipairs(prop_names) do
+ if not prototype["set_" .. prop] then
+ prototype["set_" .. prop] = function(self, value)
+ if self._private[prop] ~= value then
+ self._private[prop] = value
+ self:emit_signal("widget::redraw_needed")
+ self:emit_signal("property::" .. prop, value)
+ end
+ return self
+ end
+ end
+ if not prototype["get_" .. prop] then
+ prototype["get_" .. prop] = function(self)
+ return self._private[prop]
+ end
+ end
+ end
+end
+
+local function has_value(tab, val)
+ for _, value in ipairs(tab) do
+ if val:lower():find(value:lower(), 1, true) then
+ return true
+ end
+ end
+ return false
+end
+
+local function is_word_char(c)
+ if string.find(c, "[{[(,.:;_-+=@/ ]") then
+ return false
+ else
+ return true
+ end
+end
+
+local function cword_start(s, pos)
+ local i = pos
+ if i > 1 then
+ i = i - 1
+ end
+ while i >= 1 and not is_word_char(s:sub(i, i)) do
+ i = i - 1
+ end
+ while i >= 1 and is_word_char(s:sub(i, i)) do
+ i = i - 1
+ end
+ if i <= #s then
+ i = i + 1
+ end
+ return i
+end
+
+local function cword_end(s, pos)
+ local i = pos
+ while i <= #s and not is_word_char(s:sub(i, i)) do
+ i = i + 1
+ end
+ while i <= #s and is_word_char(s:sub(i, i)) do
+ i = i + 1
+ end
+ return i
+end
+
+local function set_mouse_cursor(cursor)
+ capi.root.cursor(cursor)
+ local wibox = capi.mouse.current_wibox
+ if wibox then
+ wibox.cursor = cursor
+ end
+end
+
+local function single_double_triple_tap(self, args)
+ local wp = self._private
+
+ if wp.click_timer == nil then
+ wp.click_timer = gtimer {
+ timeout = wp.click_timeout,
+ autostart = false,
+ call_now = false,
+ single_shot = true,
+ callback = function()
+ wp.click_count = 0
+ end
+ }
+ end
+
+ wp.click_timer:again()
+ wp.click_count = wp.click_count + 1
+ if wp.click_count == 1 then
+ args.on_single_click()
+ elseif wp.click_count == 2 then
+ args.on_double_click()
+ elseif wp.click_count == 3 then
+ args.on_triple_click()
+ wp.click_count = 0
+ end
+end
+
+local function run_keygrabber(self)
+ local wp = self._private
+ wp.keygrabber = awful.keygrabber.run(function(modifiers, key, event)
+ if event ~= "press" then
+ self:emit_signal("key::release", modifiers, key, event)
+ return
+ end
+ self:emit_signal("key::press", modifiers, key, event)
+
+ -- Convert index array to hash table
+ local mod = {}
+ for _, v in ipairs(modifiers) do
+ mod[v] = true
+ end
+
+ if mod.Control then
+ if key == "a" then
+ self:select_all()
+ elseif key == "c" then
+ self:copy()
+ elseif key == "v" then
+ self:paste()
+ elseif key == "b" or key == "Left" then
+ self:set_cursor_index_to_word_start()
+ elseif key == "f" or key == "Right" then
+ self:set_cursor_index_to_word_end()
+ elseif key == "d" then
+ self:delete_next_word()
+ elseif key == "BackSpace" then
+ self:delete_previous_word()
+ end
+ elseif mod.Shift and key:wlen() ~= 1 then
+ if key =="Left" then
+ self:decremeant_selection_end_index()
+ elseif key == "Right" then
+ self:increamant_selection_end_index()
+ end
+ else
+ if has_value(wp.unfocus_keys, key) then
+ self:unfocus()
+ end
+
+ if mod.Shift and key == "Insert" then
+ self:paste()
+ elseif key == "Home" then
+ self:set_cursor_index(0)
+ elseif key == "End" then
+ self:set_cursor_index_to_end()
+ elseif key == "BackSpace" then
+ self:delete_text()
+ elseif key == "Delete" then
+ self:delete_text_after_cursor()
+ elseif key == "Left" then
+ self:decremeant_cursor_index()
+ elseif key == "Right" then
+ self:increamant_cursor_index()
+ elseif not mod.Mod1 and not mod.Mod4 and key:wlen() == 1 then
+ self:update_text(key)
+ end
+ end
+ end)
+end
+
+function text_input:set_widget_template(widget_template)
+ local wp = self._private
+
+ wp.text_widget = widget_template:get_children_by_id("text_role")[1]
+ wp.text_widget.forced_width = math.huge
+ local text_draw = wp.text_widget.draw
+ if self:get_initial() then
+ self:replace_text(self:get_initial())
+ end
+
+ local placeholder_widget = widget_template:get_children_by_id("placeholder_role")
+ if placeholder_widget then
+ placeholder_widget = placeholder_widget[1]
+ end
+
+ function wp.text_widget:draw(context, cr, width, height)
+ local _, logical_rect = self._private.layout:get_pixel_extents()
+
+ -- Selection bg
+ cr:set_source(gcolor.change_opacity(wp.selection_bg, wp.selection_opacity))
+ cr:rectangle(
+ wp.selection_start_x,
+ logical_rect.y - 3,
+ wp.selection_end_x - wp.selection_start_x,
+ logical_rect.y + logical_rect.height + 6
+ )
+ cr:fill()
+
+ -- Cursor
+ cr:set_source(gcolor.change_opacity(wp.cursor_bg, wp.cursor_opacity))
+ cr:set_line_width(wp.cursor_width)
+ cr:move_to(wp.cursor_x, logical_rect.y - 3)
+ cr:line_to(wp.cursor_x, logical_rect.y + logical_rect.height + 6)
+ cr:stroke()
+
+ cr:set_source(gcolor(wp.text_color))
+ text_draw(self, context, cr, width, height)
+
+ if self:get_text() == "" and placeholder_widget then
+ placeholder_widget.visible = true
+ elseif placeholder_widget then
+ placeholder_widget.visible = false
+ end
+ end
+
+ local function on_drag(_, lx, ly)
+ lx, ly = wp.hierarchy:get_matrix_from_device():transform_point(lx, ly)
+ if abs(lx - wp.press_pos.lx) > 2 or abs(ly - wp.press_pos.ly) > 2 then
+ if self:get_mode() ~= "overwrite" then
+ self:set_selection_start_index_from_x_y(wp.press_pos.lx, wp.press_pos.ly)
+ end
+ self:set_selection_end_index_from_x_y(lx, ly)
+ end
+ end
+
+ wp.text_widget:connect_signal("button::press", function(_, lx, ly, button, mods, find_widgets_result)
+ if gtable.hasitem(mods, "Mod4") or button ~= 1 then
+ return
+ end
+
+ single_double_triple_tap(self, {
+ on_single_click = function()
+ self:focus()
+ self:set_cursor_index_from_x_y(lx, ly)
+ end,
+ on_double_click = function()
+ self:set_selection_to_word()
+ end,
+ on_triple_click = function()
+ self:select_all()
+ end
+ })
+
+ wp.press_pos = { lx = lx, ly = ly }
+ wp.hierarchy = find_widgets_result.hierarchy
+ find_widgets_result.drawable:connect_signal("mouse::move", on_drag)
+ end)
+
+ wp.text_widget:connect_signal("button::release", function(_, lx, ly, button, mods, find_widgets_result)
+ if button == 1 then
+ find_widgets_result.drawable:disconnect_signal("mouse::move", on_drag)
+ end
+ end)
+
+ wp.text_widget:connect_signal("mouse::enter", function()
+ set_mouse_cursor("xterm")
+ end)
+
+ wp.text_widget:connect_signal("mouse::leave", function(_, find_widgets_result)
+ if self:get_focused() == false then
+ set_mouse_cursor("left_ptr")
+ end
+
+ find_widgets_result.drawable:disconnect_signal("mouse::move", on_drag)
+ if wp.unfocus_on_mouse_leave then
+ self:unfocus()
+ end
+ end)
+
+ self:set_widget(widget_template)
+end
+
+function text_input:get_mode()
+ return self._private.mode
+end
+
+function text_input:set_focused(focused)
+ if focused == true then
+ self:focus()
+ else
+ self:unfocus()
+ end
+end
+
+function text_input:set_pattern(pattern)
+ self._private.pattern = text_input.patterns[pattern]
+end
+
+function text_input:set_obscure(obscure)
+ self._private.obscure = obscure
+ self:set_text(self:get_text())
+end
+
+function text_input:toggle_obscure()
+ self:set_obscure(not self._private.obscure)
+end
+
+function text_input:set_initial(initial)
+ self._private.initial = initial
+ self:replace_text(initial)
+end
+
+function text_input:update_text(text)
+ if self:get_mode() == "insert" then
+ self:insert_text(text)
+ else
+ self:overwrite_text(text)
+ end
+end
+
+function text_input:set_text(text)
+ self._private.text_buffer = text
+
+ if self:get_obscure() then
+ self:get_text_widget():set_text(string.rep("*", #text))
+ else
+ self:get_text_widget():set_text(text)
+ end
+end
+
+function text_input:replace_text(text)
+ self._private.text_buffer = text
+
+ if self:get_obscure() then
+ self:set_text(string.rep("*", #text))
+ else
+ self:set_text(text)
+ end
+
+ self:set_cursor_index(#text)
+end
+
+function text_input:insert_text(text)
+ local wp = self._private
+
+ local old_text = self:get_text()
+ local cursor_index = self:get_cursor_index()
+ local left_text = old_text:sub(1, cursor_index) .. text
+ local right_text = old_text:sub(cursor_index + 1)
+ local new_text = left_text .. right_text
+ if wp.pattern then
+ new_text = new_text:match(wp.pattern)
+ if new_text then
+ self:set_text(new_text)
+ self:set_cursor_index(self:get_cursor_index() + #text)
+ self:emit_signal("property::text", self:get_text())
+ end
+ else
+ self:set_text(new_text)
+ self:set_cursor_index(self:get_cursor_index() + #text)
+ self:emit_signal("property::text", self:get_text())
+ end
+end
+
+function text_input:overwrite_text(text)
+ local wp = self._private
+
+ local start_pos = wp.selection_start
+ local end_pos = wp.selection_end
+ if start_pos > end_pos then
+ start_pos, end_pos = end_pos, start_pos
+ end
+
+ local old_text = self:get_text()
+ local left_text = old_text:sub(1, start_pos)
+ local right_text = old_text:sub(end_pos + 1)
+ local new_text = left_text .. text .. right_text
+
+ if wp.pattern then
+ new_text = new_text:match(wp.pattern)
+ if new_text then
+ self:set_text(new_text)
+ self:set_cursor_index(#left_text + 1)
+ self:emit_signal("property::text", self:get_text())
+ end
+ else
+ self:set_text(new_text)
+ self:set_cursor_index(#left_text + 1)
+ self:emit_signal("property::text", self:get_text())
+ end
+end
+
+function text_input:copy()
+ local wp = self._private
+ if self:get_mode() == "overwrite" then
+ local text = self:get_text()
+ local start_pos = self._private.selection_start
+ local end_pos = self._private.selection_end
+ if start_pos > end_pos then
+ start_pos, end_pos = end_pos + 1, start_pos
+ end
+ text = text:sub(start_pos, end_pos)
+ wp.clipboard:set_text(text, -1)
+ end
+end
+
+function text_input:paste()
+ local wp = self._private
+
+ wp.clipboard:request_text(function(clipboard, text)
+ if text then
+ self:update_text(text)
+ end
+ end)
+end
+
+function text_input:delete_next_word()
+ local old_text = self:get_text()
+ local cursor_index = self:get_cursor_index()
+
+ local left_text = old_text:sub(1, cursor_index)
+ local right_text = old_text:sub(cword_end(old_text, cursor_index + 1))
+ self:set_text(left_text .. right_text)
+ self:emit_signal("property::text", self:get_text())
+end
+
+function text_input:delete_previous_word()
+ local old_text = self:get_text()
+ local cursor_index = self:get_cursor_index()
+ local wstart = cword_start(old_text, cursor_index + 1) - 1
+ local left_text = old_text:sub(1, wstart)
+ local right_text = old_text:sub(cursor_index + 1)
+ self:set_text(left_text .. right_text)
+ self:set_cursor_index(wstart)
+ self:emit_signal("property::text", self:get_text())
+end
+
+function text_input:delete_text()
+ if self:get_mode() == "insert" then
+ self:delete_text_before_cursor()
+ else
+ self:overwrite_text("")
+ end
+end
+
+function text_input:delete_text_before_cursor()
+ local cursor_index = self:get_cursor_index()
+ if cursor_index > 0 then
+ local old_text = self:get_text()
+ local left_text = old_text:sub(1, cursor_index - 1)
+ local right_text = old_text:sub(cursor_index + 1)
+ self:set_text(left_text .. right_text)
+ self:set_cursor_index(cursor_index - 1)
+ self:emit_signal("property::text", self:get_text())
+ end
+end
+
+function text_input:delete_text_after_cursor()
+ local cursor_index = self:get_cursor_index()
+ if cursor_index < #self:get_text() then
+ local old_text = self:get_text()
+ local left_text = old_text:sub(1, cursor_index)
+ local right_text = old_text:sub(cursor_index + 2)
+ self:set_text(left_text .. right_text)
+ self:emit_signal("property::text", self:get_text())
+ end
+end
+
+function text_input:get_text()
+ return self._private.text_buffer or ""
+end
+
+function text_input:get_text_widget()
+ return self._private.text_widget
+end
+
+function text_input:show_selection()
+ self._private.selection_opacity = 1
+ self:get_text_widget():emit_signal("widget::redraw_needed")
+end
+
+function text_input:hide_selection()
+ self._private.selection_opacity = 0
+ self:get_text_widget():emit_signal("widget::redraw_needed")
+end
+
+function text_input:select_all()
+ if self:get_text() == "" then
+ return
+ end
+
+ self:set_selection_start_index(0)
+ self:set_selection_end_index(#self:get_text())
+end
+
+function text_input:set_selection_to_word()
+ if self:get_text() == "" then
+ return
+ end
+
+ local word_start_index = cword_start(self:get_text(), self:get_cursor_index() + 1) - 1
+ local word_end_index = cword_end(self:get_text(), self:get_cursor_index() + 1) - 1
+
+ self:set_selection_start_index(word_start_index)
+ self:set_selection_end_index(word_end_index)
+end
+
+function text_input:set_selection_start_index(index)
+ if #self:get_text() == 0 then
+ return
+ end
+
+ index = math.max(math.min(index, #self:get_text()), 0)
+
+ local layout = self:get_text_widget()._private.layout
+ local strong_pos, weak_pos = layout:get_caret_pos(index)
+ if strong_pos then
+ self._private.selection_start = index
+ self._private.selection_start_x = strong_pos.x / Pango.SCALE
+ self._private.selection_start_y = strong_pos.y / Pango.SCALE
+ self:get_text_widget():emit_signal("widget::redraw_needed")
+ end
+end
+
+function text_input:set_selection_end_index(index)
+ if #self:get_text() == 0 then
+ return
+ end
+
+ index = math.max(math.min(index, #self:get_text()), 0)
+
+ local layout = self:get_text_widget()._private.layout
+ local strong_pos, weak_pos = layout:get_caret_pos(index)
+ if strong_pos then
+ if self:get_mode() ~= "overwrite" and index ~= self._private.selection_start then
+ self._private.mode = "overwrite"
+ self:show_selection()
+ self:hide_cursor()
+ end
+
+ self._private.selection_end = index
+ self._private.selection_end_x = strong_pos.x / Pango.SCALE
+ self._private.selection_end_y = strong_pos.y / Pango.SCALE
+ self:get_text_widget():emit_signal("widget::redraw_needed")
+ end
+end
+
+function text_input:increamant_selection_end_index()
+ if self:get_mode() == "insert" then
+ self:set_selection_start_index(self:get_cursor_index())
+ self:set_selection_end_index(self:get_cursor_index() + 1)
+ else
+ self:set_selection_end_index(self._private.selection_end + 1)
+ end
+end
+
+function text_input:decremeant_selection_end_index()
+ if self:get_mode() == "insert" then
+ self:set_selection_start_index(self:get_cursor_index())
+ self:set_selection_end_index(self:get_cursor_index() - 1)
+ else
+ self:set_selection_end_index(self._private.selection_end - 1)
+ end
+end
+
+function text_input:set_selection_start_index_from_x_y(x, y)
+ local layout = self:get_text_widget()._private.layout
+ local index, trailing = layout:xy_to_index(x * Pango.SCALE, y * Pango.SCALE)
+ if index then
+ self:set_selection_start_index(index)
+ else
+ local pixel_rect, logical_rect = self:get_text_widget()._private.layout:get_pixel_extents()
+ if x < logical_rect.x + logical_rect.width then
+ self:set_selection_start_index(0)
+ else
+ self:set_selection_start_index(#self:get_text())
+ end
+ end
+end
+
+function text_input:set_selection_end_index_from_x_y(x, y)
+ local layout = self:get_text_widget()._private.layout
+ local index, trailing = layout:xy_to_index(x * Pango.SCALE, y * Pango.SCALE)
+ if index then
+ self:set_selection_end_index(index + trailing)
+ else
+ local pixel_rect, logical_rect = self:get_text_widget()._private.layout:get_pixel_extents()
+ if x < logical_rect.x + logical_rect.width then
+ self:set_selection_end_index(0)
+ else
+ self:set_selection_end_index(#self:get_text())
+ end
+ end
+end
+
+function text_input:show_cursor()
+ self._private.cursor_opacity = 1
+ self:get_text_widget():emit_signal("widget::redraw_needed")
+end
+
+function text_input:hide_cursor()
+ self._private.cursor_opacity = 0
+ self:get_text_widget():emit_signal("widget::redraw_needed")
+end
+
+function text_input:set_cursor_index(index)
+ index = math.max(math.min(index, #self:get_text()), 0)
+
+ local layout = self:get_text_widget()._private.layout
+ local strong_pos, weak_pos = layout:get_cursor_pos(index)
+ if strong_pos then
+ if strong_pos == self._private.cursor_index and self._private.mode == "insert" then
+ return
+ end
+
+ if self:get_focused() and self:get_mode() ~= "insert" then
+ self:show_cursor()
+ end
+
+ self._private.cursor_index = index
+ self._private.mode = "insert"
+
+ self._private.cursor_x = strong_pos.x / Pango.SCALE
+ self._private.cursor_y = strong_pos.y / Pango.SCALE
+
+ self:hide_selection()
+
+ self:get_text_widget():emit_signal("widget::redraw_needed")
+ end
+end
+
+function text_input:set_cursor_index_from_x_y(x, y)
+ local layout = self:get_text_widget()._private.layout
+ local index, trailing = layout:xy_to_index(x * Pango.SCALE, y * Pango.SCALE)
+
+ if index then
+ self:set_cursor_index(index)
+ else
+ local pixel_rect, logical_rect = self:get_text_widget()._private.layout:get_pixel_extents()
+ if x < logical_rect.x + logical_rect.width then
+ self:set_cursor_index(0)
+ else
+ self:set_cursor_index(#self:get_text())
+ end
+ end
+end
+
+function text_input:set_cursor_index_to_word_start()
+ self:set_cursor_index(cword_start(self:get_text(), self:get_cursor_index() + 1) - 1)
+end
+
+function text_input:set_cursor_index_to_word_end()
+ self:set_cursor_index(cword_end(self:get_text(), self:get_cursor_index() + 1) - 1)
+end
+
+function text_input:set_cursor_index_to_end()
+ self:set_cursor_index(#self:get_text())
+end
+
+function text_input:increamant_cursor_index()
+ if self:get_mode() == "insert" then
+ self:set_cursor_index(self:get_cursor_index() + 1)
+ else
+ local start_pos = self._private.selection_start
+ local end_pos = self._private.selection_end
+ if start_pos > end_pos then
+ start_pos, end_pos = end_pos, start_pos
+ end
+ self:set_cursor_index(end_pos)
+ end
+end
+
+function text_input:decremeant_cursor_index()
+ if self:get_mode() == "insert" then
+ self:set_cursor_index(self:get_cursor_index() - 1)
+ else
+ local start_pos = self._private.selection_start
+ local end_pos = self._private.selection_end
+ if start_pos > end_pos then
+ start_pos, end_pos = end_pos, start_pos
+ end
+ self:set_cursor_index(start_pos)
+ end
+end
+
+function text_input:get_cursor_index()
+ return self._private.cursor_index
+end
+
+function text_input:set_focus_on_subject_mouse_enter(subject)
+ subject:connect_signal("mouse::enter", function()
+ self:focus()
+ end)
+end
+
+function text_input:set_unfocus_on_subject_mouse_leave(subject)
+ subject:connect_signal("mouse::leave", function()
+ self:unfocus()
+ end)
+end
+
+function text_input:get_focused()
+ return self._private.focused
+end
+
+function text_input:focus()
+ local wp = self._private
+
+ if self:get_focused() == true then
+ return
+ end
+
+ -- Do it first, so the cursor won't change back when unfocus was called on the focused text input
+ capi.awesome.emit_signal("text_input::focus", self)
+
+ set_mouse_cursor("xterm")
+
+ if self:get_mode() == "insert" then
+ self:show_cursor()
+ end
+
+ run_keygrabber(self)
+
+ if wp.cursor_blink then
+ if wp.cursor_blink_timer == nil then
+ wp.cursor_blink_timer = gtimer {
+ timeout = wp.cursor_blink_rate,
+ autostart = false,
+ call_now = false,
+ single_shot = false,
+ callback = function()
+ if self._private.cursor_opacity == 1 then
+ self:hide_cursor()
+ elseif self:get_mode() == "insert" then
+ self:show_cursor()
+ end
+ end
+ }
+ end
+ wp.cursor_blink_timer:start()
+ end
+
+
+ wp.focused = true
+ self:emit_signal("focus")
+end
+
+function text_input:unfocus(context)
+ local wp = self._private
+ if self:get_focused() == false then
+ return
+ end
+
+ set_mouse_cursor("left_ptr")
+ self:hide_cursor()
+ wp.cursor_blink_timer:stop()
+ self:hide_selection()
+ if self.reset_on_unfocus == true then
+ self:replace_text("")
+ end
+
+ awful.keygrabber.stop(wp.keygrabber)
+ wp.focused = false
+ self:emit_signal("unfocus", context or "normal", self:get_text())
+end
+
+function text_input:toggle()
+ local wp = self._private
+
+ if self:get_focused() == false then
+ self:focus()
+ else
+ self:unfocus()
+ end
+end
+
+local function new()
+ local widget = wibox.container.background()
+ gtable.crush(widget, text_input, true)
+
+ local wp = widget._private
+
+ wp.focused = false
+ wp.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
+ wp.cursor_index = 0
+ wp.mode = "insert"
+ wp.click_count = 0
+
+ wp.cursor_x = 0
+ wp.cursor_y = 0
+ wp.cursor_opacity = 0
+ wp.selection_start_x = 0
+ wp.selection_end_x = 0
+ wp.selection_start_y = 0
+ wp.selection_end_y = 0
+ wp.selection_opacity = 0
+
+ wp.click_timeout = 0.2
+
+ wp.unfocus_keys = { "Escape", "Return" }
+ wp.unfocus_on_root_clicked = true
+ wp.unfocus_on_client_clicked = true
+ wp.unfocus_on_mouse_leave = false
+ wp.unfocus_on_tag_change = true
+ wp.unfocus_on_other_text_input_focus = true
+ wp.unfocus_on_client_focus = true
+
+ wp.focus_on_subject_mouse_enter = nil
+ wp.unfocus_on_subject_mouse_leave = nil
+
+ wp.reset_on_unfocus = false
+
+ wp.pattern = nil
+ wp.obscure = false
+
+ wp.placeholder = ""
+ wp.text_color = qcolor.palette.fg()
+ wp.text = ""
+
+ wp.cursor_width = 2
+ wp.cursor_bg = qcolor.palette.fg()
+ wp.cursor_blink = true
+ wp.cursor_blink_rate = 0.6
+
+ wp.selection_bg = qcolor.palette.bg.high
+
+ widget:set_widget_template(wibox.widget {
+ layout = wibox.layout.stack,
+ {
+ widget = wibox.widget.textbox,
+ id = "placeholder_role",
+ text = wp.placeholder
+ },
+ {
+ widget = wibox.widget.textbox,
+ id = "text_role",
+ text = wp.text
+ }
+ })
+
+ capi.tag.connect_signal("property::selected", function()
+ if wp.unfocus_on_tag_change then
+ widget:unfocus()
+ end
+ end)
+
+ capi.awesome.connect_signal("text_input::focus", function(text_input)
+ if wp.unfocus_on_other_text_input_focus and text_input ~= widget then
+ widget:unfocus()
+ end
+ end)
+
+ capi.client.connect_signal("focus", function()
+ if wp.unfocus_on_client_focus then
+ widget:unfocus()
+ end
+ end)
+
+ awful.mouse.append_global_mousebindings({
+ awful.button({"Any"}, 1, function()
+ if wp.unfocus_on_root_clicked then
+ widget:unfocus()
+ end
+ end),
+ awful.button({"Any"}, 3, function()
+ if wp.unfocus_on_root_clicked then
+ widget:unfocus()
+ end
+ end)
+ })
+
+ capi.client.connect_signal("button::press", function()
+ if wp.unfocus_on_client_clicked then
+ widget:unfocus()
+ end
+ end)
+
+ capi.awesome.connect_signal("colorscheme::changed", function(old_colorscheme_to_new_map)
+ wp.text_color = old_colorscheme_to_new_map[wp.text_color]
+ wp.cursor_bg = old_colorscheme_to_new_map[wp.cursor_bg]
+ wp.selection_bg = old_colorscheme_to_new_map[wp.selection_bg]
+ end)
+
+ return widget
+end
+
+function text_input.mt:__call(...)
+ return new()
+end
+
+build_properties(text_input, properties)
+
+return setmetatable(text_input, text_input.mt)