aboutsummaryrefslogtreecommitdiff
path: root/.config/awesome/ui/fresnel/init.lua
diff options
context:
space:
mode:
authordelta <darkussdelta@gmail.com>2025-10-29 16:35:38 +0100
committerdelta <darkussdelta@gmail.com>2025-10-29 16:35:38 +0100
commitd7c66522cf365f516babcfeb1d4a2d36c3ea41af (patch)
tree30c7d6103037b31170ae6d8fd58e3849e3cea823 /.config/awesome/ui/fresnel/init.lua
parentdf418700b7d776f03ee5b58daa2d497cddb45aca (diff)
a small refactor
Diffstat (limited to '.config/awesome/ui/fresnel/init.lua')
-rw-r--r--.config/awesome/ui/fresnel/init.lua472
1 files changed, 297 insertions, 175 deletions
diff --git a/.config/awesome/ui/fresnel/init.lua b/.config/awesome/ui/fresnel/init.lua
index 5d091b6..dc8e333 100644
--- a/.config/awesome/ui/fresnel/init.lua
+++ b/.config/awesome/ui/fresnel/init.lua
@@ -1,5 +1,6 @@
-local al_prompt = require "lib.bling.widget.app_launcher.prompt"
+local text_input = require "ui.fresnel.text_input"
local awful = require "awful"
+local qdebug = require "quarrel.debug"
local cfg = require "misc.cfg"
local gshape = require "gears.shape"
local gtable = require "gears.table"
@@ -13,19 +14,142 @@ local rubato = require "lib.rubato"
local wibox = require "wibox"
local btn = awful.button.names
-local max_entries = 10
+local MAX_VISIBLE_ENTRIES = 10
+local DEFAULT_SCROLL_AMOUNT = 0
+local DEFAULT_SELECTED_IDX = 1
-local fresnel = {}
+---@class (exact) Fresnel
+---@field private _toggled boolean Whether fresnel is open
+---@field private _entries (Entry|string)[]
+---@field private _scroll_amount integer How many entries down are we
+---@field private _prev_scroll_amount integer The _scroll_amount of the previous cycle
+---@field private _visible_entries Entry[] The currently visible entries
+---@field private _selected_idx integer The index of the currently selected entry. Is in the range 1..
+---@field private _prev_selected_idx integer The _selected_idx of the previous cycle
+---@field private _active_lenses integer The number of lense titles in the _entries table
+---@field private _lense_header_positions integer[] The positions of all the lense titles
+---@field private _prev_text string
+---@field private _first_query boolean
+---@field private _make_header fun(string): wibox.container.background
+---@field private _w_popup wibox.widget.base
+---@field private _w_prompt wibox.widget.base
+---@field private _w_status wibox.widget.base
+---@field private _l_entries wibox.widget.base
+---@field private _t_opacity table
+---@field private _t_height table
+local fresnel = {
+ _toggled = false,
+ _entries = {},
+ _scroll_amount = DEFAULT_SCROLL_AMOUNT,
+ _prev_scroll_amount = DEFAULT_SCROLL_AMOUNT,
+ _visible_entries = {},
+ _selected_idx = DEFAULT_SELECTED_IDX,
+ _prev_selected_idx = DEFAULT_SELECTED_IDX,
+ _lense_header_positions = {}
+}
-fresnel._toggled = false
-fresnel._entries_exec = {}
-fresnel._entries_offset = 0
-fresnel._entries_count = 0
-fresnel._visible_entries = 0
-fresnel._selected_index = 1
+---@private
+---@param lense_name string
+function fresnel._make_header(lense_name)
+ return wibox.widget {
+ widget = wibox.container.background,
+ fg = qcolor.palette.fg.low,
+ {
+ widget = wibox.container.margin,
+ margins = qui.PADDING,
+ {
+ {
+ widget = wibox.container.place,
+ {
+ widget = wibox.container.background,
+ bg = qcolor.palette.border(),
+ forced_height = qui.BORDER_WIDTH,
+ forced_width = qui.BIG_PADDING * 2,
+ },
+ },
+ {
+ widget = wibox.container.constraint,
+ strategy = "max",
+ height = qui.CHAR_HEIGHT,
+ {
+ widget = wibox.widget.textbox,
+ text = lense_name,
+ },
+ },
+ {
+ widget = wibox.container.place,
+ fill_horizontal = true,
+ content_fill_horizontal = true,
+ {
+ widget = wibox.container.background,
+ bg = qcolor.palette.border(),
+ forced_height = qui.BORDER_WIDTH,
+ },
+ },
+ layout = wibox.layout.fixed.horizontal,
+ spacing = qui.BIG_PADDING,
+ },
+ },
+ }
+end
-function fresnel:_exec_entry(entry_index)
- local exec = self._entries_exec[entry_index]
+---@private
+---@param entry Entry
+function fresnel:_make_entry(entry)
+ ---@class FresnelEntry : wibox.widget.base
+ ---@field _selected boolean
+ local entry_widget = wibox.widget {
+ widget = wibox.container.background,
+ shape = qui.shape,
+ {
+ widget = wibox.container.margin,
+ margins = qui.PADDING,
+ {
+ widget = wibox.container.constraint,
+ strategy = "max",
+ height = qui.CHAR_HEIGHT,
+ {
+ widget = wibox.widget.textbox,
+ text = entry.message,
+ },
+ },
+ },
+ buttons = {
+ awful.button {
+ modifiers = {},
+ button = btn.LEFT,
+ on_press = function()
+ fresnel:_exec_entry(entry)
+ end,
+ },
+ },
+ _selected = false,
+ }
+
+ entry_widget:connect_signal("mouse::enter", function()
+ if entry_widget._selected == true then
+ return
+ end
+ entry_widget.bg = qcolor.palette.bg.high
+ end)
+
+ entry_widget:connect_signal("mouse::leave", function()
+ if entry_widget._selected == true then
+ return
+ end
+ entry_widget.bg = qcolor.palette.bg()
+ end)
+
+ return entry_widget
+end
+
+---@private
+---@param entry integer|Entry
+function fresnel:_exec_entry(entry)
+ if type(entry) == "number" then
+ entry = self._visible_entries[entry]
+ end
+ local exec = entry.exec
if type(exec) ~= "userdata" and type(exec) ~= "nil" then
if exec[2] then
awful.spawn(cfg.terminal .. " -e /bin/sh -c " .. exec[1] .. " 1>/dev/null 2>&1")
@@ -35,172 +159,166 @@ function fresnel:_exec_entry(entry_index)
end
end
-function fresnel:_update(query, scrolled)
+---@private
+---@param query string?
+function fresnel:_update(query)
query = query or ""
- scrolled = scrolled or false
+ self._entries = {}
+ self._lense_header_positions = {}
+ self._active_lenses = 0
- if not scrolled then
- self._selected_index = 1
- self._entries_offset = 0
+ for _, lense in ipairs(qnative.lenses) do
+ local entries = lense:query(query)
+ if type(entries) == "table" then
+ table.insert(self._entries, lense.name)
+ table.insert(self._lense_header_positions, #self._entries)
+ if #entries > 0 then -- Entry[]
+ self._entries = gtable.join(self._entries, entries)
+ elseif entries.message then -- Entry
+ table.insert(self._entries, entries)
+ end
+ self._active_lenses = self._active_lenses + 1
+ end -- either empty Entry[] or nil, in which case we shouldn't display them
end
- local layout = self._popup.widget:get_children_by_id("entry_layout")[1]
- local status = self._popup.widget:get_children_by_id("status")[1]
- local all_providers = {}
- local entries_count = 0
+ -- reset the scroll
+ if type(self._entries[1]) == "string" then
+ self._selected_idx = 2 -- to avoid selecting the header first
+ else
+ self._selected_idx = DEFAULT_SELECTED_IDX
+ end
+ self._scroll_amount = DEFAULT_SCROLL_AMOUNT
+ fresnel:_render(true)
+ self._first_query = false
+end
- self._entries_exec = {}
- layout:reset()
+---@private
+---@param update boolean
+function fresnel:_render(update)
+ local visible_entries_end = math.min(self._scroll_amount + MAX_VISIBLE_ENTRIES, #self._entries)
+ self._visible_entries = qtable.slice(self._entries, self._scroll_amount + 1, visible_entries_end)
- for _, provider in qtable.opairs(qnative.lenses) do
- local entries = provider(query)
+ local layout = fresnel._l_entries
- table.sort(entries, function(a, b)
- return a.message < b.message
- end)
+ if self._scroll_amount ~= self._prev_scroll_amount or update or self._first_query then
+ layout:reset()
+ for i, entry in ipairs(self._visible_entries) do
+ if type(entry) == "string" then
+ layout:add(self._make_header(entry))
+ else
+ local entry_widget = self:_make_entry(entry)
+ if self._selected_idx == i then
+ entry_widget._selected = true
+ entry_widget.bg = qcolor.palette.bg.high
+ end
+ layout:add(entry_widget)
+ end
+ end
+ elseif self._selected_idx ~= self._prev_selected_idx then
+ local entry_widget = layout.children[self._selected_idx] --[[@as FresnelEntry ]]
+ entry_widget._selected = true
+ entry_widget.bg = qcolor.palette.bg.high
- all_providers = gtable.join(all_providers, entries)
+ local prev_entry_widget = layout.children[self._prev_selected_idx] --[[@as FresnelEntry ]]
+ prev_entry_widget._selected = false
+ prev_entry_widget.bg = qcolor.palette.bg()
end
- self._entries_count = #all_providers
-
- for i, entry in ipairs(all_providers) do
- if i > self._entries_offset then
- if entries_count == max_entries then
- break
- end
- table.insert(self._entries_exec, entry.exec)
-
- local entry_widget = wibox.widget {
- widget = wibox.container.background,
- shape = qui.shape,
- {
- widget = wibox.container.margin,
- margins = qui.PADDING,
- {
- widget = wibox.container.constraint,
- strategy = "max",
- height = qui.CHAR_HEIGHT,
- {
- {
- widget = wibox.container.background,
- fg = qcolor.palette.low,
- {
- widget = wibox.widget.textbox,
- text = entry.provider .. " | ",
- },
- },
- {
- widget = wibox.widget.textbox,
- text = entry.message,
- },
- spacing = qui.PADDING,
- layout = wibox.layout.fixed.horizontal,
- },
- },
- },
- buttons = {
- awful.button {
- modifiers = {},
- button = btn.LEFT,
- on_press = function()
- self:_exec_entry(i)
- end,
- },
- },
- _selected = false,
- }
+ local headers_passed = 0
+ local current_position = self._scroll_amount + self._selected_idx
+ for _, position in ipairs(self._lense_header_positions) do
+ if current_position > position then
+ headers_passed = headers_passed + 1
+ end
+ end
+ self._w_status.text = (self._scroll_amount + self._selected_idx - headers_passed) .. "/" .. (#self._entries - self._active_lenses)
+ self._prev_scroll_amount = self._scroll_amount
+ self._prev_selected_idx = self._selected_idx
+end
- if self._selected_index + self._entries_offset == i then
- entry_widget._selected = true
- entry_widget.bg = qcolor.palette.bg.high
- end
+---@param up boolean Whether to scroll up or down
+function fresnel:scroll(up)
+ local direction = up and -1 or 1
+ local offset = direction
- entry_widget:connect_signal("mouse::enter", function()
- if entry_widget._selected == true then
- return
- end
- entry_widget.bg = qcolor.palette.bg.high
- end)
- entry_widget:connect_signal("mouse::leave", function()
- if entry_widget._selected == true then
- return
- end
- entry_widget.bg = qcolor.palette.bg()
- end)
+ if type(self._entries[self._scroll_amount + self._selected_idx + offset]) == "string" then
+ offset = offset + direction
+ end
- layout:add(entry_widget)
+ local new_selected_idx = self._selected_idx + offset
+ local new_position = self._scroll_amount + new_selected_idx
+ local new_scroll_amount = self._scroll_amount + offset
- entries_count = entries_count + 1
- self._visible_entries = entries_count
+ if new_position < 1 then
+ if self._scroll_amount > 0 then
+ self._scroll_amount = self._scroll_amount + direction
+ self._selected_idx = self._selected_idx - direction
+ self:_render(false)
end
+ return
+ elseif new_position > #self._entries then
+ return
+ end
+
+ if up and new_selected_idx <= 0 then
+ self._scroll_amount = new_scroll_amount
+ elseif not up and new_selected_idx > #self._visible_entries then
+ self._scroll_amount = new_scroll_amount
+ else
+ self._selected_idx = new_selected_idx
end
- status.text = self._entries_offset + self._selected_index .. "/" .. self._entries_count
+ self:_render(false)
end
-fresnel._text = ""
-fresnel._prompt = al_prompt {
- prompt = "",
- reset_on_stop = true,
- ul_cursor = "low",
- bg_cursor = qcolor.palette.bg.high,
- changed_callback = function(text)
- if fresnel._text == text then
- return
- end
- if fresnel._toggled == false then
- return
- end
- fresnel:_update(text)
- fresnel._text = text
- end,
- keypressed_callback = function(mod, key)
- if key == "Escape" or key == " " and mod.Mod4 then
- fresnel:hide()
- elseif key == "Return" then
- fresnel:_exec_entry(fresnel._selected_index)
- fresnel:hide()
- elseif key == "Up" then
- local next_index = fresnel._selected_index - 1
- if next_index < 1 and fresnel._entries_offset == 0 then
- return
- elseif next_index < 1 and fresnel._entries_offset > 0 then
- fresnel._entries_offset = fresnel._entries_offset - 1
- else
- fresnel._selected_index = next_index
- end
+fresnel._prev_text = ""
+fresnel._w_prompt = wibox.widget {
+ widget = text_input,
+ reset_on_unfocus = true,
+ unfocus_keys = {},
+}
- fresnel:_update(fresnel._text, true)
- elseif key == "Down" then
- local next_index = fresnel._selected_index + 1
- if
- next_index > fresnel._visible_entries
- and fresnel._entries_offset + fresnel._visible_entries == fresnel._entries_count
- then
- return
- elseif
- next_index > fresnel._visible_entries
- and fresnel._entries_offset + fresnel._visible_entries < fresnel._entries_count
- then
- fresnel._entries_offset = fresnel._entries_offset + 1
- else
- fresnel._selected_index = next_index
- end
+fresnel._w_prompt:connect_signal("unfocus", function ()
+ fresnel:_hide()
+end)
- fresnel:_update(fresnel._text, true)
- end
- end,
-}
+fresnel._w_prompt:connect_signal("property::text", function(_, text)
+ fresnel:_update(text)
+end)
+
+fresnel._w_prompt:connect_signal("key::press", function(_, _mods, key)
+ -- Convert index array to hash table
+ local mods = {}
+ for _, v in ipairs(_mods) do
+ mods[v] = true
+ end
+
+ if key == "Escape" or key == " " and mods.Mod4 then
+ fresnel:hide()
+ elseif key == "Return" then
+ fresnel:_exec_entry(fresnel._selected_idx)
+ fresnel:hide()
+ elseif key == "k" and mods.Mod1 then
+ fresnel:scroll(true)
+ elseif key == "j" and mods.Mod1 then
+ fresnel:scroll(false)
+ end
+end)
local max_height = qui.BIG_PADDING * 2 + (qui.BIG_PADDING * 2 + qui.CHAR_HEIGHT) * 10
--- + qui.PADDING
--- + qui.PADDING * 9
local width = awful.screen.focused().geometry.width / 2
-fresnel._popup = qui.popup {
+fresnel._l_entries = wibox.widget {
+ spacing = qui.PADDING,
+ layout = wibox.layout.fixed.vertical,
+}
+fresnel._w_status = wibox.widget {
+ widget = wibox.widget.textbox,
+ text = "0/0",
+}
+fresnel._w_popup = qui.popup {
-- visible = false,
ontop = true,
placement = false,
@@ -222,7 +340,7 @@ fresnel._popup = qui.popup {
{
{
widget = wibox.widget.textbox,
- text = ">",
+ markup = [[<span foreground="]] .. qcolor.palette.yellow() .. [[">></span>]],
},
{
widget = wibox.container.margin,
@@ -237,15 +355,11 @@ fresnel._popup = qui.popup {
{
widget = wibox.container.background,
fg = qcolor.palette.fg(),
- fresnel._prompt.textbox,
+ fresnel._w_prompt,
},
},
},
- {
- widget = wibox.widget.textbox,
- text = "0/0",
- id = "status",
- },
+ fresnel._w_status,
layout = wibox.layout.align.horizontal,
},
},
@@ -255,52 +369,60 @@ fresnel._popup = qui.popup {
margins = {
top = qui.PADDING,
},
- {
- spacing = qui.PADDING,
- layout = wibox.layout.fixed.vertical,
- id = "entry_layout",
- },
+ fresnel._l_entries,
},
layout = wibox.layout.align.vertical,
},
}
+
function fresnel:show()
+ self._prev_scroll_amount = DEFAULT_SCROLL_AMOUNT
+ self._prev_selected_idx = DEFAULT_SELECTED_IDX
+ self._first_query = true
self._toggled = true
- self._opacity_timed.target = 1
- self._height_timed:set(max_height)
+ self._t_opacity.target = 1
+ self._t_height:set(max_height)
self:_update()
- self._prompt:start()
+ self._w_prompt:focus()
end
-function fresnel:hide()
+---@private
+function fresnel:_hide()
self._toggled = false
- self._opacity_timed.target = 0
- self._height_timed:set(0)
- self._prompt:stop()
+ self._t_opacity.target = 0
+ self._t_height:set(0)
+ for _, lense in ipairs(qnative.lenses) do
+ lense:interrupt()
+ end
+end
+
+function fresnel:hide()
+ self._w_prompt:unfocus()
+ self:_hide()
end
-fresnel._height_timed = qanim:new {
+fresnel._t_height = qanim:new {
duration = qvars.anim_duration,
pos = 0,
easing = qvars.easing,
subscribed = function(pos)
- fresnel._popup.shape = function(cr, w)
+ fresnel._w_popup.shape = function(cr, w)
gshape.partially_rounded_rect(cr, w, pos, false, false, true, true, qui.BORDER_RADIUS)
end
end,
}
-- TODO: optimize the search algo to be more efficient and not require making fresnel invisible
-fresnel._opacity_timed = rubato.timed {
+fresnel._t_opacity = rubato.timed {
duration = qvars.anim_duration,
pos = 0,
subscribed = function(pos)
- fresnel._popup.opacity = pos
+ fresnel._w_popup.opacity = pos
if pos == 0 then
- fresnel._popup.visible = false
+ fresnel._w_popup.visible = false
else
- fresnel._popup.visible = true
+ fresnel._w_popup.visible = true
end
end,
}