diff options
author | delta <darkussdelta@gmail.com> | 2025-07-04 00:38:29 +0200 |
---|---|---|
committer | delta <darkussdelta@gmail.com> | 2025-07-04 00:38:29 +0200 |
commit | b3530d7c4a102935fa26498a160ee1dc6c1e9c03 (patch) | |
tree | d7751206a694bc5de2d6b34b0c077cfcd1855798 /.config/awesome/quarrel | |
parent | df75ec5ed5e3848c497f0439acb43ec9246ad3e7 (diff) |
:3
Diffstat (limited to '.config/awesome/quarrel')
36 files changed, 2626 insertions, 852 deletions
diff --git a/.config/awesome/quarrel/animation/bezier.lua b/.config/awesome/quarrel/animation/bezier.lua new file mode 100644 index 0000000..a505e48 --- /dev/null +++ b/.config/awesome/quarrel/animation/bezier.lua @@ -0,0 +1,89 @@ +local gtable = require "gears.table" + +-- port of https://github.com/WebKit/WebKit/blob/da934454c84ac2dcbf9fca9e5f4ac2644ef25d72/Source/WebCore/platform/graphics/UnitBezier.h + +local bezier = {} + +function bezier:sample_x(t) + -- `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. + return ((self.ax * t + self.bx) * t + self.cx) * t +end + +function bezier:sample_y(t) + return ((self.ay * t + self.by) * t + self.cy) * t +end + +function bezier:sample_derivative_x(t) + return (3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx +end + +function bezier:solve_x(x, epsilon) + local x2, d2 + local t2 = x + + -- First try a few iterations of Newton's method -- normally very fast. + for _ = 1, 8 do + x2 = self:sample_x(t2) - x + if math.abs(x2) < epsilon then + return t2 + end + d2 = self:sample_derivative_x(t2) + if math.abs(d2) < 1e-6 then + break + end + t2 = t2 - x2 / d2 + end + + -- Fall back to the bisection method for reliability. + local t0 = 0 + local t1 = 1 + t2 = x + + if t2 < t0 then + return t0 + end + if t2 > t1 then + return t1 + end + + while t0 < t1 do + x2 = self:sample_x(t2) + if math.abs(x2 - x) < epsilon then + return t2 + end + if x > x2 then + t0 = t2 + else + t1 = t2 + end + t2 = (t1 - t0) * 0.5 + t0 + end + + -- Failure. + return t2 +end + +function bezier:solve(x, epsilon) + return self:sample_y(self:solve_x(x, epsilon)) +end + +local function new(x1, y1, x2, y2) + local obj = gtable.crush({}, bezier) + + -- Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1). + obj.cx = 3.0 * x1 + obj.bx = 3.0 * (x2 - x1) - obj.cx + obj.ax = 1.0 - obj.cx - obj.bx + + obj.cy = 3.0 * y1 + obj.by = 3.0 * (y2 - y1) - obj.cy + obj.ay = 1.0 - obj.cy - obj.by + + return obj +end + +return setmetatable(bezier, { + __call = function(_, ...) + return new(...) + end, +}) diff --git a/.config/awesome/quarrel/animation/init.lua b/.config/awesome/quarrel/animation/init.lua new file mode 100644 index 0000000..c57b01a --- /dev/null +++ b/.config/awesome/quarrel/animation/init.lua @@ -0,0 +1,249 @@ +------------------------------------------- +-- @author https://github.com/Kasper24 +-- @copyright 2021-2025 Kasper24 +------------------------------------------- +local GLib = require("lgi").GLib +local gobject = require "gears.object" +local gpcall = require "gears.protected_call" +local gtable = require "gears.table" +local gtimer = require "gears.timer" +local subscribable = require "quarrel.animation.subscribable" +-- local qconsts = require "quarrel.consts" +local qtween = require "quarrel.animation.tween" +-- local qbezier = require "quarrel.animation.bezier" +local ipairs = ipairs +local table = table +local pairs = pairs + +local animation_manager = {} + +-- local easing_bezier = qbezier(0.2, 0, 0, 1) +-- animation_manager.consts = qconsts.protect(function() +-- local duration = 0.3 +-- return { +-- DURATION = duration, +-- INTRO = duration / 4, +-- EASING = function(t, b, c, d) +-- local epsilon = 1000 / d +-- return c * easing_bezier:solve(t/d, epsilon) + b +-- end +-- } +-- end) + +local animation = {} + +local function second_to_micro(sec) + return sec * 1000000 +end + +local function framerate_tomilli(framerate) + return 1000 / framerate +end + +local function on_no_running_animations(self, callback) + gtimer.start_new(0.1, function() + if #self._private.animations <= 0 then + callback() + return false + else + local has_non_looped_anim = false + for _, animation in ipairs(self._private.animations) do + if animation.loop == false then + has_non_looped_anim = true + end + end + + if has_non_looped_anim == false then + callback() + return false + end + end + + return true + end) +end + +local function animation_loop(self) + self._private.source_id = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + framerate_tomilli(self._private.framerate), + function() + for index, animation in ipairs(self._private.animations) do + if animation.state == true then + -- compute delta time + local time = GLib.get_monotonic_time() + local delta = time - animation.last_elapsed + animation.last_elapsed = time + + -- If pos is true, the animation has ended + local pos = gpcall(animation.tween.update, animation.tween, delta) + if pos == true then + -- Loop the animation, don't end it. + -- Useful for widgets like the spinning cicle + if animation.loop == true then + animation.tween:reset() + else + animation.state = false + + -- Snap to end + animation.pos = animation.tween.target + + gpcall(animation.emit_signal, animation, "update", animation.pos) + gpcall(animation.fire, animation, animation.pos) + + gpcall(animation.emit_signal, animation, "ended", animation.pos) + gpcall(animation.ended.fire, animation, animation.pos) + + table.remove(self._private.animations, index) + end + -- Animation in process, keep updating + else + animation.pos = pos + + gpcall(animation.emit_signal, animation, "update", animation.pos) + gpcall(animation.fire, animation, animation.pos) + end + else + table.remove(self._private.animations, index) + end + end + + -- call again the function after cooldown + return true + end + ) +end + +function animation:set(args) + args = args or {} + + -- Awestoer/Rubbto compatibility + -- I'd rather this always be a table, but Awestore/Rubbto + -- except the :set() method to have 1 number value parameter + -- used to set the target + local is_table = type(args) == "table" + local initial = is_table and (args.pos or self.pos) or self.pos + local subject = is_table and (args.subject or self.subject) or self.subject + local target = is_table and (args.target or self.target) or args + local duration = is_table and (args.duration or self.duration) or self.duration + local easing = is_table and (args.easing or self.easing) or self.easing + + if self.tween == nil or self.reset_on_stop == true then + self.tween = qtween.new { + initial = initial, + subject = subject, + target = target, + duration = second_to_micro(duration), + easing = easing, + } + end + + if self._private.anim_manager._private.instant and self.override_instant ~= true then + self.pos = self.tween.target + self:fire(self.pos) + self:emit_signal("update", self.pos) + + self.state = false + self.ended:fire(self.pos) + self:emit_signal("ended", self.pos) + return + end + + if self._private.anim_manager._private.animations[self.index] == nil then + table.insert(self._private.anim_manager._private.animations, self) + end + + self.state = true + self.last_elapsed = GLib.get_monotonic_time() + + self.started:fire() + self:emit_signal "started" +end + +-- Rubato compatibility +function animation:abort() + self.state = false +end + +function animation:stop() + self.state = false +end + +function animation:initial() + return self._private.initial +end + +function animation_manager:set_instant(value) + if value == true and self._private.instant == false then + on_no_running_animations(self, function() + -- GLib.source_remove(self._private.source_id) + self._private.instant = true + end) + elseif self._private.instant == true then + self._private.instant = false + -- animation_loop(self) + end +end + +function animation_manager:set_framerate(value) + self._private.framerate = value + -- if self._private.instant == false then + on_no_running_animations(self, function() + GLib.source_remove(self._private.source_id) + animation_loop(self) + end) + -- end +end + +function animation_manager:new(args) + args = args or {} + + args.pos = args.pos or 0 + args.subject = args.subject or nil + args.target = args.target or nil + args.duration = args.duration or 0 + args.easing = args.easing or nil + args.loop = args.loop or false + args.signals = args.signals or {} + args.update = args.update or nil + args.reset_on_stop = args.reset_on_stop == nil and true or args.reset_on_stop + + -- Awestoer/Rubbto compatibility + args.subscribed = args.subscribed or nil + local ret = subscribable() + ret.started = subscribable() + ret.ended = subscribable() + if args.subscribed ~= nil then + ret:subscribe(args.subscribed) + end + + for sig, sigfun in pairs(args.signals) do + ret:connect_signal(sig, sigfun) + end + if args.update ~= nil then + ret:connect_signal("update", args.update) + end + + gtable.crush(ret, args, true) + gtable.crush(ret, animation, true) + + ret._private = {} + ret._private.anim_manager = self + ret._private.initial = args.pos + -- Can't have it private for rubato compatibility + ret.state = false + + return ret +end + +local instance = gobject {} +gtable.crush(instance, animation_manager, true) + +instance._private = {} +instance._private.animations = {} +instance._private.instant = false +instance._private.framerate = 60 + +animation_loop(instance) + +return instance diff --git a/.config/awesome/quarrel/animation/subscribable/init.lua b/.config/awesome/quarrel/animation/subscribable/init.lua new file mode 100644 index 0000000..9542704 --- /dev/null +++ b/.config/awesome/quarrel/animation/subscribable/init.lua @@ -0,0 +1,47 @@ +------------------------------------------- +-- @author https://github.com/Kasper24 +-- @copyright 2021-2025 Kasper24 +-- Adopted from rubato +------------------------------------------- +local gobject = require "gears.object" + +-- Kidna copying awesotre's stores on a surface level for added compatibility +local function subscribable(args) + local ret = gobject {} + local subscribed = {} + + -- Subscrubes a function to the object so that it's called when `fire` is + -- Calls subscribe_callback if it exists as well + function ret:subscribe(func) + local id = tostring(func):gsub("function: ", "") + subscribed[id] = func + + if self.subscribe_callback then + self.subscribe_callback(func) + end + end + + -- Unsubscribes a function and calls unsubscribe_callback if it exists + function ret:unsubscribe(func) + if not func then + subscribed = {} + else + local id = tostring(func):gsub("function: ", "") + subscribed[id] = nil + end + + if self.unsubscribe_callback then + self.unsubscribe_callback(func) + end + end + + function ret:fire(...) + for _, func in pairs(subscribed) do + func(...) + end + end + + return ret +end + +return subscribable diff --git a/.config/awesome/quarrel/animation/tween/init.lua b/.config/awesome/quarrel/animation/tween/init.lua new file mode 100644 index 0000000..5f4ce51 --- /dev/null +++ b/.config/awesome/quarrel/animation/tween/init.lua @@ -0,0 +1,554 @@ +------------------------------------------- +-- @author https://github.com/Kasper24 +-- @copyright 2021-2025 Kasper24 +------------------------------------------- +-- easing +-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits. +-- For all easing functions: +-- t = time == how much time has to pass for the tweening to complete +-- b = begin == starting property value +-- c = change == ending - beginning +-- d = duration == running time. How much time has passed *right now* +-- local Color = require "external.lua-color" +local gobject = require "gears.object" +local gtable = require "gears.table" +local tostring = tostring +local assert = assert +local table = table +local pairs = pairs +local error = error + +local type = type + +local tween = { + _VERSION = "tween 2.1.1", + _DESCRIPTION = "tweening for lua", + _URL = "https://github.com/kikito/tween.lua", + _LICENSE = [[ + MIT LICENSE + Copyright (c) 2014 Enrique GarcĂa Cota, Yuichi Tateno, Emmanuel Oga + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]], +} + +local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin + +-- linear +local function linear(t, b, c, d) + return c * t / d + b +end + +-- quad +local function inQuad(t, b, c, d) + return c * pow(t / d, 2) + b +end +local function outQuad(t, b, c, d) + t = t / d + return -c * t * (t - 2) + b +end +local function inOutQuad(t, b, c, d) + t = t / d * 2 + if t < 1 then + return c / 2 * pow(t, 2) + b + end + return -c / 2 * ((t - 1) * (t - 3) - 1) + b +end +local function outInQuad(t, b, c, d) + if t < d / 2 then + return outQuad(t * 2, b, c / 2, d) + end + return inQuad((t * 2) - d, b + c / 2, c / 2, d) +end + +-- cubic +local function inCubic(t, b, c, d) + return c * pow(t / d, 3) + b +end +local function outCubic(t, b, c, d) + return c * (pow(t / d - 1, 3) + 1) + b +end +local function inOutCubic(t, b, c, d) + t = t / d * 2 + if t < 1 then + return c / 2 * t * t * t + b + end + t = t - 2 + return c / 2 * (t * t * t + 2) + b +end +local function outInCubic(t, b, c, d) + if t < d / 2 then + return outCubic(t * 2, b, c / 2, d) + end + return inCubic((t * 2) - d, b + c / 2, c / 2, d) +end + +-- quart +local function inQuart(t, b, c, d) + return c * pow(t / d, 4) + b +end +local function outQuart(t, b, c, d) + return -c * (pow(t / d - 1, 4) - 1) + b +end +local function inOutQuart(t, b, c, d) + t = t / d * 2 + if t < 1 then + return c / 2 * pow(t, 4) + b + end + return -c / 2 * (pow(t - 2, 4) - 2) + b +end +local function outInQuart(t, b, c, d) + if t < d / 2 then + return outQuart(t * 2, b, c / 2, d) + end + return inQuart((t * 2) - d, b + c / 2, c / 2, d) +end + +-- quint +local function inQuint(t, b, c, d) + return c * pow(t / d, 5) + b +end +local function outQuint(t, b, c, d) + return c * (pow(t / d - 1, 5) + 1) + b +end +local function inOutQuint(t, b, c, d) + t = t / d * 2 + if t < 1 then + return c / 2 * pow(t, 5) + b + end + return c / 2 * (pow(t - 2, 5) + 2) + b +end +local function outInQuint(t, b, c, d) + if t < d / 2 then + return outQuint(t * 2, b, c / 2, d) + end + return inQuint((t * 2) - d, b + c / 2, c / 2, d) +end + +-- sine +local function inSine(t, b, c, d) + return -c * cos(t / d * (pi / 2)) + c + b +end +local function outSine(t, b, c, d) + return c * sin(t / d * (pi / 2)) + b +end +local function inOutSine(t, b, c, d) + return -c / 2 * (cos(pi * t / d) - 1) + b +end +local function outInSine(t, b, c, d) + if t < d / 2 then + return outSine(t * 2, b, c / 2, d) + end + return inSine((t * 2) - d, b + c / 2, c / 2, d) +end + +-- expo +local function inExpo(t, b, c, d) + if t == 0 then + return b + end + return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001 +end +local function outExpo(t, b, c, d) + if t == d then + return b + c + end + return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b +end +local function inOutExpo(t, b, c, d) + if t == 0 then + return b + end + if t == d then + return b + c + end + t = t / d * 2 + if t < 1 then + return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 + end + return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b +end +local function outInExpo(t, b, c, d) + if t < d / 2 then + return outExpo(t * 2, b, c / 2, d) + end + return inExpo((t * 2) - d, b + c / 2, c / 2, d) +end + +-- circ +local function inCirc(t, b, c, d) + return (-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) +end +local function outCirc(t, b, c, d) + return (c * sqrt(1 - pow(t / d - 1, 2)) + b) +end +local function inOutCirc(t, b, c, d) + t = t / d * 2 + if t < 1 then + return -c / 2 * (sqrt(1 - t * t) - 1) + b + end + t = t - 2 + return c / 2 * (sqrt(1 - t * t) + 1) + b +end +local function outInCirc(t, b, c, d) + if t < d / 2 then + return outCirc(t * 2, b, c / 2, d) + end + return inCirc((t * 2) - d, b + c / 2, c / 2, d) +end + +-- elastic +local function calculatePAS(p, a, c, d) + p, a = p or d * 0.3, a or 0 + if a < abs(c) then + return p, c, p / 4 + end -- p, a, s + return p, a, p / (2 * pi) * asin(c / a) -- p,a,s +end +local function inElastic(t, b, c, d, a, p) + local s + if t == 0 then + return b + end + t = t / d + if t == 1 then + return b + c + end + p, a, s = calculatePAS(p, a, c, d) + t = t - 1 + return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b +end +local function outElastic(t, b, c, d, a, p) + local s + if t == 0 then + return b + end + t = t / d + if t == 1 then + return b + c + end + p, a, s = calculatePAS(p, a, c, d) + return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b +end +local function inOutElastic(t, b, c, d, a, p) + local s + if t == 0 then + return b + end + t = t / d * 2 + if t == 2 then + return b + c + end + p, a, s = calculatePAS(p, a, c, d) + t = t - 1 + if t < 0 then + return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b + end + return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) * 0.5 + c + b +end +local function outInElastic(t, b, c, d, a, p) + if t < d / 2 then + return outElastic(t * 2, b, c / 2, d, a, p) + end + return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p) +end + +-- back +local function inBack(t, b, c, d, s) + s = s or 1.70158 + t = t / d + return c * t * t * ((s + 1) * t - s) + b +end +local function outBack(t, b, c, d, s) + s = s or 1.70158 + t = t / d - 1 + return c * (t * t * ((s + 1) * t + s) + 1) + b +end +local function inOutBack(t, b, c, d, s) + s = (s or 1.70158) * 1.525 + t = t / d * 2 + if t < 1 then + return c / 2 * (t * t * ((s + 1) * t - s)) + b + end + t = t - 2 + return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b +end +local function outInBack(t, b, c, d, s) + if t < d / 2 then + return outBack(t * 2, b, c / 2, d, s) + end + return inBack((t * 2) - d, b + c / 2, c / 2, d, s) +end + +-- bounce +local function outBounce(t, b, c, d) + t = t / d + if t < 1 / 2.75 then + return c * (7.5625 * t * t) + b + end + if t < 2 / 2.75 then + t = t - (1.5 / 2.75) + return c * (7.5625 * t * t + 0.75) + b + elseif t < 2.5 / 2.75 then + t = t - (2.25 / 2.75) + return c * (7.5625 * t * t + 0.9375) + b + end + t = t - (2.625 / 2.75) + return c * (7.5625 * t * t + 0.984375) + b +end +local function inBounce(t, b, c, d) + return c - outBounce(d - t, 0, c, d) + b +end +local function inOutBounce(t, b, c, d) + if t < d / 2 then + return inBounce(t * 2, 0, c, d) * 0.5 + b + end + return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b +end +local function outInBounce(t, b, c, d) + if t < d / 2 then + return outBounce(t * 2, b, c / 2, d) + end + return inBounce((t * 2) - d, b + c / 2, c / 2, d) +end + +tween.easing = { + linear = linear, + inQuad = inQuad, + outQuad = outQuad, + inOutQuad = inOutQuad, + outInQuad = outInQuad, + inCubic = inCubic, + outCubic = outCubic, + inOutCubic = inOutCubic, + outInCubic = outInCubic, + inQuart = inQuart, + outQuart = outQuart, + inOutQuart = inOutQuart, + outInQuart = outInQuart, + inQuint = inQuint, + outQuint = outQuint, + inOutQuint = inOutQuint, + outInQuint = outInQuint, + inSine = inSine, + outSine = outSine, + inOutSine = inOutSine, + outInSine = outInSine, + inExpo = inExpo, + outExpo = outExpo, + inOutExpo = inOutExpo, + outInExpo = outInExpo, + inCirc = inCirc, + outCirc = outCirc, + inOutCirc = inOutCirc, + outInCirc = outInCirc, + inElastic = inElastic, + outElastic = outElastic, + inOutElastic = inOutElastic, + outInElastic = outInElastic, + inBack = inBack, + outBack = outBack, + inOutBack = inOutBack, + outInBack = outInBack, + inBounce = inBounce, + outBounce = outBounce, + inOutBounce = inOutBounce, + outInBounce = outInBounce, +} + +-- Private interface +local function copyTables(destination, keysTable, valuesTable) + valuesTable = valuesTable or keysTable + local mt = getmetatable(keysTable) + if mt and getmetatable(destination) == nil then + setmetatable(destination, mt) + end + + for k, v in pairs(keysTable) do + if type(v) == "table" then + destination[k] = copyTables({}, v, valuesTable[k]) + else + destination[k] = valuesTable[k] + end + end + return destination +end + +local function checkSubjectAndTargetRecursively(subject, target, path) + path = path or {} + local targetType, newPath + for k, targetValue in pairs(target) do + targetType, newPath = type(targetValue), copyTables({}, path) + table.insert(newPath, tostring(k)) + if targetType == "number" then + assert( + type(subject[k]) == "number", + "Parameter '" .. table.concat(newPath, "/") .. "' is missing from subject or isn't a number" + ) + elseif targetType == "table" then + checkSubjectAndTargetRecursively(subject[k], targetValue, newPath) + else + assert( + targetType == "number", + "Parameter '" .. table.concat(newPath, "/") .. "' must be a number or table of numbers" + ) + end + end +end + +local function checkNewParams(initial, duration, subject, target, easing) + -- assert(type(initial) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) + -- assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) + assert(type(easing) == "function", "easing must be a function. Was " .. tostring(easing)) + + if subject and target then + local tsubject = type(subject) + assert( + tsubject == "table" or tsubject == "userdata", + "subject must be a table or userdata. Was " .. tostring(subject) + ) + assert(type(target) == "table", "target must be a table. Was " .. tostring(target)) + checkSubjectAndTargetRecursively(subject, target) + end +end + +local function getEasingFunction(easing) + easing = easing or "linear" + if type(easing) == "string" then + local name = easing + easing = tween.easing[name] + if type(easing) ~= "function" then + error("The easing function name '" .. name .. "' is invalid") + end + end + return easing +end + +local function performEasingOnSubject(subject, target, initial, clock, duration, easing) + local t, b, c, d + for k, v in pairs(target) do + if type(v) == "table" then + performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing) + else + t, b, c, d = clock, initial[k], v - initial[k], duration + subject[k] = easing(t, b, c, d) + end + end +end + +-- local function performEasingOnColor(initial, target, clock, duration, easing) +-- initial = Color(initial) +-- target = Color(target) +-- +-- local r = easing(clock, initial.r, target.r - initial.r, duration) +-- local g = easing(clock, initial.g, target.g - initial.g, duration) +-- local b = easing(clock, initial.b, target.b - initial.b, duration) +-- local a = easing(clock, initial.a, target.a - initial.a, duration) +-- +-- return tostring(Color { r = r, g = g, b = b, a = a }) +-- end + +local function performEasing(table, initial, target, clock, duration, easing) + if type(target) == "table" then + local t, b, c, d + for k, target in pairs(target) do + if type(target) == "table" then + table[k] = {} + performEasing(table[k], initial[k], target, clock, duration, easing) + -- elseif type(target) == "string" and target:sub(1, 1) == "#" then + -- table[k] = performEasingOnColor(initial[k], target, clock, duration, easing) + else + t, b, c, d = clock, initial[k], target - initial[k], duration + table[k] = easing(t, b, c, d) + end + end + + return table + -- elseif type(target) == "string" and target:sub(1, 1) == "#" then + -- return performEasingOnColor(initial, target, clock, duration, easing) + else + local t, b, c, d = clock, initial, target - initial, duration + return easing(t, b, c, d) + end +end + +-- Public interface +local Tween = {} + +function Tween:set(clock) + assert(type(clock) == "number", "clock must be a positive number or 0") + + if self.subject and self.initial == 0 then + self.initial = copyTables({}, self.target, self.subject) + end + + self.clock = clock + + if self.clock <= 0 then + self.clock = 0 + if self.subject then + copyTables(self.subject, self.initial) + end + elseif self.clock >= self.duration then -- the tween has expired + self.clock = self.duration + + if self.subject then + copyTables(self.subject, self.target) + end + else + if self.subject then + performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing) + else + local pos = {} + return performEasing(pos, self.initial, self.target, self.clock, self.duration, self.easing) + end + end + + return self.clock >= self.duration +end + +function Tween:update(dt) + assert(type(dt) == "number", "dt must be a number") + return self:set(self.clock + dt) +end + +function Tween:reset() + return self:set(0) +end + +function tween.new(args) + args = args or {} + + args.initial = args.initial or 0 + args.subject = args.subject or nil + args.target = args.target or nil + args.duration = args.duration or 0 + args.easing = args.easing or nil + + args.easing = getEasingFunction(args.easing) + checkNewParams(args.initial, args.duration, args.subject, args.target, args.easing) + + local ret = gobject {} + ret.clock = 0 + + gtable.crush(ret, args, true) + gtable.crush(ret, Tween, true) + + return ret +end + +return tween diff --git a/.config/awesome/quarrel/bezier.lua b/.config/awesome/quarrel/bezier.lua deleted file mode 100644 index 4229961..0000000 --- a/.config/awesome/quarrel/bezier.lua +++ /dev/null @@ -1,343 +0,0 @@ ---------------------------------------------------------------------------- ---- A helper module for computations involving BĂ©zier curves --- --- @author Alex Belykh <albel727@ngs.ru> --- @copyright 2021 Alex Belykh --- @submodule gears.math ---------------------------------------------------------------------------- - -local table_insert = table.insert - -local bezier = {} - ---- Compute the value of a BĂ©zier curve at a given value of the t parameter. --- --- This function evaluates the given curve `B` of an arbitrary degree --- at a given point t. --- --- @tparam {number,...} c The table of control points of the curve. --- @tparam number t The value of the t parameter to evaluate the curve at. --- @treturn[1] number The value of `B(t)`. --- @treturn[2] nil `nil`, if c is empty. --- @staticfct gears.math.bezier.curve_evaluate_at --- @see wibox.widget.graph.step_hook -function bezier.curve_evaluate_at(c, t) - local from = c - local tmp = { nil, nil, nil, nil } - while #from > 1 do - for i = 1, #from - 1 do - tmp[i] = from[i] * (1 - t) + from[i + 1] * t - end - tmp[#from] = nil - from = tmp - end - - return from[1] -end - ---- Split a BĂ©zier curve into two curves at a given value of the t parameter. --- --- This function splits the given curve `B` of an arbitrary degree at a point t --- into two curves of the same degree `B_left` and `B_right`, such that --- `B_left(0)=B(0)`, `B_left(1)=B(t)=B_right(0)`, `B_right(1)=B(1)`. --- --- @tparam {number,...} c The table of control points of the curve. --- @tparam number t The value of the t parameter to split the curve at. --- @treturn {number,...} The table of control points for `B_left`. --- @treturn {number,...} The table of control points for `B_right`. --- @staticfct gears.math.bezier.curve_split_at --- @see wibox.widget.graph.step_hook -function bezier.curve_split_at(c, t) - local coefs_left, coefs_right = {}, {} - local from = c - local tmp = { nil, nil, nil, nil } - while #from > 0 do - table_insert(coefs_left, from[1]) - table_insert(coefs_right, 1, from[#from]) - for i = 1, #from - 1 do - tmp[i] = from[i] * (1 - t) + from[i + 1] * t - end - tmp[#from] = nil - from = tmp - end - - return coefs_left, coefs_right -end - ---- Get the n-th derivative BĂ©zier curve of a BĂ©zier curve. --- --- This function computes control points for the curve that is --- the derivative of order `n` in `t`, i.e. `B^(n)(t)`, --- of the given curve `B(t)` of an arbitrary degree. --- --- @tparam {number,...} c The table of control points of the curve. --- @tparam[opt=1] integer n The order of the derivative to take. --- @treturn[1] {number,...} The table of control points of `B^(n)(t)`. --- @treturn[2] nil If n is less than 0. --- @staticfct gears.math.bezier.curve_derivative --- @see wibox.widget.graph.step_hook -function bezier.curve_derivative(c, n) - n = n or 1 - if n < 0 then - return - end - if n < 1 then - return c - end - local c_len = #c - if c_len < n + 1 then - return {} - end - - local from = c - local tmp = {} - - for l = c_len - 1, c_len - n, -1 do - for i = 1, l do - tmp[i] = (from[i + 1] - from[i]) * l - end - tmp[l + 1] = nil - from = tmp - end - - return from -end - --- This is used instead of plain 0 to try and be compatible --- with objects that implement their own arithmetic via metatables. -local function get_zero(c, zero) - return c and c * 0 or zero -end - ---- Compute the value of the n-th derivative of a BĂ©zier curve ---- at a given value of the t parameter. --- --- This is roughly the same as --- `curve_evaluate_at(curve_derivative(c, n), t)`, but the latter --- would throw errors or return nil instead of 0 in some cases. --- --- @tparam {number,...} c The table of control points of the curve. --- @tparam number t The value of the t parameter to compute the derivative at. --- @tparam[opt=1] integer n The order of the derivative to take. --- @tparam[opt=nil] number|nil zero The value to return if c is empty. --- @treturn[1] number The value of `B^(n)(t)`. --- @treturn[2] nil nil, if n is less than 0. --- @treturn[3] number|nil The value of the zero parameter, if c is empty. --- @staticfct gears.math.bezier.curve_derivative_at --- @see wibox.widget.graph.step_hook -function bezier.curve_derivative_at(c, t, n, zero) - local d = bezier.curve_derivative(c, n) - if not d then - return - end - - return bezier.curve_evaluate_at(d, t) or get_zero(c[1], zero) -end - ---- Compute the value of the 1-st derivative of a BĂ©zier curve at t=0. --- --- This is the same as `curve_derivative_at(c, 0)`, but since it's particularly --- computationally simple and useful in practice, it has its own function. --- --- @tparam {number,...} c The table of control points of the curve. --- @tparam[opt=nil] number|nil zero The value to return if c is empty. --- @treturn[1] number The value of `B'(0)`. --- @treturn[2] number|nil The value of the zero parameter, if c is empty. --- @staticfct gears.math.bezier.curve_derivative_at_zero --- @see wibox.widget.graph.step_hook -function bezier.curve_derivative_at_zero(c, zero) - local l = #c - if l < 2 then - return get_zero(c[1], zero) - end - return (c[2] - c[1]) * (l - 1) -end - ---- Compute the value of the 1-st derivative of a BĂ©zier curve at t=1. --- --- This is the same as `curve_derivative_at(c, 1)`, but since it's particularly --- computationally simple and useful in practice, it has its own function. --- --- @tparam {number,...} c The table of control points of the curve. --- @tparam[opt=nil] number|nil zero The value to return if c is empty. --- @treturn[1] number The value of `B'(1)`. --- @treturn[2] number|nil The value of the zero parameter, if c is empty. --- @staticfct gears.math.bezier.curve_derivative_at_one --- @see wibox.widget.graph.step_hook -function bezier.curve_derivative_at_one(c, zero) - local l = #c - if l < 2 then - return get_zero(c[1], zero) - end - return (c[l] - c[l - 1]) * (l - 1) -end - ---- Get the (n+1)-th degree BĂ©zier curve, that has the same shape as --- a given n-th degree BĂ©zier curve. --- --- Given the control points of a curve B of degree n, this function computes --- the control points for the curve Q, such that `Q(t) = B(t)`, and --- Q has the degree n+1, i.e. it has one control point more. --- --- @tparam {number,...} c The table of control points of the curve B. --- @treturn {number,...} The table of control points of the curve Q. --- @staticfct gears.math.bezier.curve_elevate_degree --- @see wibox.widget.graph.step_hook -function bezier.curve_elevate_degree(c) - local ret = { c[1] } - local len = #c - - for i = 1, len - 1 do - ret[i + 1] = (i * c[i] + (len - i) * c[i + 1]) / len - end - - ret[len + 1] = c[len] - return ret -end - ---- Get a cubic BĂ©zier curve that passes through given points (up to 4). --- --- This function takes up to 4 values and returns the 4 control points --- for a cubic curve --- --- `B(t) = c0\*(1-t)^3 + 3\*c1\*t\*(1-t)^2 + 3\*c2\*t^2\*(1-t) + c3\*t^3`, --- that takes on these values at equidistant values of the t parameter. --- --- If only p0 is given, `B(0)=B(1)=B(for all t)=p0`. --- --- If p0 and p1 are given, `B(0)=p0` and `B(1)=p1`. --- --- If p0, p1 and p2 are given, `B(0)=p0`, `B(1/2)=p1` and `B(1)=p2`. --- --- For 4 points given, `B(0)=p0`, `B(1/3)=p1`, `B(2/3)=p2`, `B(1)=p3`. --- --- @tparam number p0 --- @tparam[opt] number p1 --- @tparam[opt] number p2 --- @tparam[opt] number p3 --- @treturn number c0 --- @treturn number c1 --- @treturn number c2 --- @treturn number c3 --- @staticfct gears.math.bezier.cubic_through_points --- @see wibox.widget.graph.step_hook -function bezier.cubic_through_points(p0, p1, p2, p3) - if not p1 then - return p0, p0, p0, p0 - end - if not p2 then - local c1 = (2 * p0 + p1) / 3 - local c2 = (2 * p1 + p0) / 3 - return p0, c1, c2, p1 - end - if not p3 then - local c1 = (4 * p1 - p2) / 3 - local c2 = (4 * p1 - p0) / 3 - return p0, c1, c2, p2 - end - local c1 = (-5 * p0 + 18 * p1 - 9 * p2 + 2 * p3) / 6 - local c2 = (-5 * p3 + 18 * p2 - 9 * p1 + 2 * p0) / 6 - return p0, c1, c2, p3 -end - ---- Get a cubic BĂ©zier curve with given values and derivatives at endpoints. --- --- This function computes the 4 control points for the cubic curve B, such that --- `B(0)=p0`, `B'(0)=d0`, `B(1)=p3`, `B'(1)=d3`. --- --- @tparam number d0 The value of the derivative at t=0. --- @tparam number p0 The value of the curve at t=0. --- @tparam number p3 The value of the curve at t=1. --- @tparam number d3 The value of the derivative at t=1. --- @treturn number c0 --- @treturn number c1 --- @treturn number c2 --- @treturn number c3 --- @staticfct gears.math.bezier.cubic_from_points_and_derivatives --- @see wibox.widget.graph.step_hook -function bezier.cubic_from_points_and_derivatives(d0, p0, p3, d3) - local c1 = p0 + d0 / 3 - local c2 = p3 - d3 / 3 - return p0, c1, c2, p3 -end - ---- Get a cubic BĂ©zier curve with given values at endpoints and starting ---- derivative, while minimizing (an approximation of) the stretch energy. --- --- This function computes the 4 control points for the cubic curve B, such that --- `B(0)=p0`, `B'(0)=d0`, `B(1)=p3`, and --- the integral of `(B'(t))^2` on `t=[0,1]` is minimal. --- (The actual stretch energy is the integral of `|B'(t)|`) --- --- In practical terms this is almost the same as "the curve of shortest length --- connecting given points and having the given starting speed". --- --- @tparam number d0 The value of the derivative at t=0. --- @tparam number p0 The value of the curve at t=0. --- @tparam number p3 The value of the curve at t=1. --- @treturn number c0 --- @treturn number c1 --- @treturn number c2 --- @treturn number c3 --- @staticfct gears.math.bezier.cubic_from_derivative_and_points_min_stretch --- @see wibox.widget.graph.step_hook -function bezier.cubic_from_derivative_and_points_min_stretch(d0, p0, p3) - local c1 = p0 + d0 / 3 - local c2 = (2 * p0 - c1 + 3 * p3) / 4 - return p0, c1, c2, p3 -end - ---- Get a cubic BĂ©zier curve with given values at endpoints and starting ---- derivative, while minimizing (an approximation of) the strain energy. --- --- This function computes the 4 control points for the cubic curve B, such that --- `B(0)=p0`, `B'(0)=d0`, `B(1)=p3`, and --- the integral of `(B''(t))^2` on `t=[0,1]` is minimal. --- --- In practical terms this is almost the same as "the curve of smallest --- speed change connecting given points and having the given starting speed". --- --- @tparam number d0 The value of the derivative at t=0. --- @tparam number p0 The value of the curve at t=0. --- @tparam number p3 The value of the curve at t=1. --- @treturn number c0 --- @treturn number c1 --- @treturn number c2 --- @treturn number c3 --- @staticfct gears.math.bezier.cubic_from_derivative_and_points_min_strain --- @see wibox.widget.graph.step_hook -function bezier.cubic_from_derivative_and_points_min_strain(d, p0, p3) - local c1 = p0 + d / 3 - local c2 = (c1 + p3) / 2 - return p0, c1, c2, p3 -end - ---- Get a cubic BĂ©zier curve with given values at endpoints and starting ---- derivative, while minimizing the jerk energy. --- --- This function computes the 4 control points for the cubic curve B, such that --- `B(0)=p0`, `B'(0)=d0`, `B(1)=p3`, and --- the integral of `(B'''(t))^2` on `t=[0,1]` is minimal. --- --- In practical terms this is almost the same as "the curve of smallest --- acceleration change connecting given points and having the given --- starting speed". --- --- @tparam number d0 The value of the derivative at t=0. --- @tparam number p0 The value of the curve at t=0. --- @tparam number p3 The value of the curve at t=1. --- @treturn number c0 --- @treturn number c1 --- @treturn number c2 --- @treturn number c3 --- @staticfct gears.math.bezier.cubic_from_derivative_and_points_min_jerk --- @see wibox.widget.graph.step_hook -function bezier.cubic_from_derivative_and_points_min_jerk(d, p0, p3) - local c1 = p0 + d / 3 - local c2 = c1 + (p3 - p0) / 3 - return p0, c1, c2, p3 -end - -return bezier - --- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/.config/awesome/quarrel/bind/consts.lua b/.config/awesome/quarrel/bind/consts.lua new file mode 100644 index 0000000..5a91d84 --- /dev/null +++ b/.config/awesome/quarrel/bind/consts.lua @@ -0,0 +1,27 @@ +local awful = require "awful" + +local C = {} + +-- taken from https://github.com/bew/dotfiles/blob/ab9bb1935783f7a31ef777b1d7e26d53f35df864/gui/wezterm/cfg_utils.lua +C.mods = setmetatable({ _SHORT_MAP = { C = "Control", S = "Shift", A = "Mod1", M = "Mod4" } }, { + __index = function(self, key) + local resolved_mods = {} + for i = 1, #key do + resolved_mods[i] = self._SHORT_MAP[key:sub(i, i)] + end + return resolved_mods + end, +}) + +local btns = awful.button.names + +---@enum buttons +C.btns = { + left = btns.LEFT, + right = btns.RIGHT, + middle = btns.MIDDLE, + up = btns.SCROLL_UP, + down = btns.SCROLL_DOWN, +} + +return C diff --git a/.config/awesome/quarrel/bind.lua b/.config/awesome/quarrel/bind/init.lua index a1abf29..a851440 100644 --- a/.config/awesome/quarrel/bind.lua +++ b/.config/awesome/quarrel/bind/init.lua @@ -1,11 +1,10 @@ +local C = require "quarrel.bind.consts" local awful = require "awful" local gtable = require "gears.table" -local qstore = require "quarrel.store" ----@class QuarrelBind -local qbind = {} - -qstore.bindings = {} +local M = gtable.crush({ + bindings = {}, +}, C) ---@alias mouse_button ---| 0 Left mouse button @@ -35,7 +34,7 @@ qstore.bindings = {} ---@field press fun(...) | fun(any, ...) Function to run when the trigger is pressed ---@field desc string? Description ---@field group string? What group the binding will show up in ----@field triggers Trigger[] | bind +---@field triggers Trigger[] | bind[] | bind ---Get the corresponding binding creation function for a trigger ---@param bind bind @@ -83,24 +82,30 @@ end --- Create a new binding ---@param binding Binding ---@return awful.key[] -function qbind:new(binding) +function M:new(binding) if not binding.hidden then - table.insert(qstore.bindings, binding) + table.insert(self.bindings, binding) end binding.mods = binding.mods or {} local awful_bindings = {} if type(binding.triggers) == "table" then - for _, trigger in - ipairs(binding.triggers --[[@as Trigger[]]) + for _, _trigger in + ipairs(binding.triggers --[[@as bind[] | Trigger[]]) do + local trigger + if type(_trigger) == "table" then + trigger = _trigger + elseif type(_trigger) == "string" or type(_trigger) == "number" then + trigger = { _trigger, _trigger } + end table.insert(awful_bindings, translate_binding(binding, trigger, true)) end elseif type(binding.triggers) == "string" or type(binding.triggers) == "number" then return translate_binding(binding, binding.triggers --[[@as bind]], false) else - error "binding.triggers can only be a string or a table" + error("binding.triggers can only be a string or a table (passed in " .. type(binding.triggers) .. ")") end -- for some reason multi-trigger bindings only work if i do this @@ -109,4 +114,6 @@ function qbind:new(binding) return gtable.join(table.unpack(awful_bindings)) end -return qbind +return setmetatable(M, { + __call = M.new, +}) diff --git a/.config/awesome/quarrel/color.lua b/.config/awesome/quarrel/color.lua new file mode 100644 index 0000000..b96ef57 --- /dev/null +++ b/.config/awesome/quarrel/color.lua @@ -0,0 +1,64 @@ +-- autogenerated from palette.json +local M = {} + +local function tone_set(colors) + return setmetatable(colors, { + __call = function(self) + return self[1] + end, + }) +end + +M.palette = { + bg = tone_set { + high = "#171c22", + highest = "#1d2228", + low = "#0c1116", + lowest = "#070c11", + "#12161c", + }, + blue = tone_set { + bright = "#abe4ff", + "#8bc3fc", + }, + border = tone_set { + "#323b46", + variant = "#262f39", + }, + cyan = tone_set { + bright = "#8ef4e2", + "#6dd3c2", + }, + fg = tone_set { + high = "#e8eff8", + low = "#a8afb7", + "#c8ced7", + }, + green = tone_set { + bright = "#abf3b3", + "#8bd294", + }, + orange = tone_set { + bright = "#ffc08e", + "#ff9f6f", + }, + pink = tone_set { + bright = "#ffccd4", + "#e5acb4", + }, + purple = tone_set { + bright = "#e4d1ff", + "#c4b1f6", + }, + red = tone_set { + bright = "#ffb2a9", + "#ff928a", + }, + yellow = tone_set { + bright = "#ffd278", + "#ecb256", + }, + transparent = "#00000000", +} + +return M diff --git a/.config/awesome/quarrel/const.lua b/.config/awesome/quarrel/const.lua new file mode 100644 index 0000000..342ae41 --- /dev/null +++ b/.config/awesome/quarrel/const.lua @@ -0,0 +1,22 @@ +local M = {} + +---@param protected (fun(): table) | table +function M.protect(protected) + ---@type table + local tbl + if type(protected) == "table" then + tbl = protected + elseif type(protected) == "function" then + tbl = protected() + else + error("expected a table or a function that returns a table, got " .. type(protected), 2) + end + return setmetatable({}, { + __index = tbl, + __newindex = function(_, k, v) + error("attempted to change constant " .. tostring(k) .. " to " .. tostring(v), 2) + end, + }) +end + +return M diff --git a/.config/awesome/quarrel/debugger.lua b/.config/awesome/quarrel/debugger.lua new file mode 100644 index 0000000..b1a272a --- /dev/null +++ b/.config/awesome/quarrel/debugger.lua @@ -0,0 +1,865 @@ +-- SPDX-License-Identifier: MIT +-- Copyright (c) 2024 Scott Lembcke and Howling Moon Software + +local dbg + +-- Use ANSI color codes in the prompt by default. +local COLOR_GRAY = "" +local COLOR_RED = "" +local COLOR_BLUE = "" +local COLOR_YELLOW = "" +local COLOR_RESET = "" +local GREEN_CARET = " => " + +local function pretty(obj, max_depth) + if max_depth == nil then + max_depth = dbg.pretty_depth + end + + -- Returns true if a table has a __tostring metamethod. + local function coerceable(tbl) + local meta = getmetatable(tbl) + return (meta and meta.__tostring) + end + + local function recurse(obj, depth) + if type(obj) == "string" then + -- Dump the string so that escape sequences are printed. + return string.format("%q", obj) + elseif type(obj) == "table" and depth < max_depth and not coerceable(obj) then + local str = "{" + + for k, v in pairs(obj) do + local pair = pretty(k, 0) .. " = " .. recurse(v, depth + 1) + str = str .. (str == "{" and pair or ", " .. pair) + end + + return str .. "}" + else + -- tostring() can fail if there is an error in a __tostring metamethod. + local success, value = pcall(function() + return tostring(obj) + end) + return (success and value or "<!!error in __tostring metamethod!!>") + end + end + + return recurse(obj, 0) +end + +-- The stack level that cmd_* functions use to access locals or info +-- The structure of the code very carefully ensures this. +local CMD_STACK_LEVEL = 6 + +-- Location of the top of the stack outside of the debugger. +-- Adjusted by some debugger entrypoints. +local stack_top = 0 + +-- The current stack frame index. +-- Changed using the up/down commands +local stack_inspect_offset = 0 + +-- LuaJIT has an off by one bug when setting local variables. +local LUA_JIT_SETLOCAL_WORKAROUND = 0 + +-- Default dbg.read function +local function dbg_read(prompt) + dbg.write(prompt) + io.flush() + return io.read() +end + +-- Default dbg.write function +local function dbg_write(str) + io.write(str) +end + +local function dbg_writeln(str, ...) + if select("#", ...) == 0 then + dbg.write((str or "<NULL>") .. "\n") + else + dbg.write(string.format(str .. "\n", ...)) + end +end + +local function format_loc(file, line) + return COLOR_BLUE .. file .. COLOR_RESET .. ":" .. COLOR_YELLOW .. line .. COLOR_RESET +end +local function format_stack_frame_info(info) + local filename = info.source:match "@(.*)" + local source = filename and dbg.shorten_path(filename) or info.short_src + local namewhat = (info.namewhat == "" and "chunk at" or info.namewhat) + local name = ( + info.name and "'" .. COLOR_BLUE .. info.name .. COLOR_RESET .. "'" or format_loc(source, info.linedefined) + ) + return format_loc(source, info.currentline) .. " in " .. namewhat .. " " .. name +end + +local repl + +-- Return false for stack frames without source, +-- which includes C frames, Lua bytecode, and `loadstring` functions +local function frame_has_line(info) + return info.currentline >= 0 +end + +local function hook_factory(repl_threshold) + return function(offset, reason) + return function(event, _) + -- Skip events that don't have line information. + if not frame_has_line(debug.getinfo(2)) then + return + end + + -- Tail calls are specifically ignored since they also will have tail returns to balance out. + if event == "call" then + offset = offset + 1 + elseif event == "return" and offset > repl_threshold then + offset = offset - 1 + elseif event == "line" and offset <= repl_threshold then + repl(reason) + end + end + end +end + +local hook_step = hook_factory(1) +local hook_next = hook_factory(0) +local hook_finish = hook_factory(-1) + +-- Create a table of all the locally accessible variables. +-- Globals are not included when running the locals command, but are when running the print command. +local function local_bindings(offset, include_globals) + local level = offset + stack_inspect_offset + CMD_STACK_LEVEL + local func = debug.getinfo(level).func + local bindings = {} + + -- Retrieve the upvalues + do + local i = 1 + while true do + local name, value = debug.getupvalue(func, i) + if not name then + break + end + bindings[name] = value + i = i + 1 + end + end + + -- Retrieve the locals (overwriting any upvalues) + do + local i = 1 + while true do + local name, value = debug.getlocal(level, i) + if not name then + break + end + bindings[name] = value + i = i + 1 + end + end + + -- Retrieve the varargs (works in Lua 5.2 and LuaJIT) + local varargs = {} + do + local i = 1 + while true do + local name, value = debug.getlocal(level, -i) + if not name then + break + end + varargs[i] = value + i = i + 1 + end + end + if #varargs > 0 then + bindings["..."] = varargs + end + + if include_globals then + -- In Lua 5.2, you have to get the environment table from the function's locals. + local env = (_VERSION <= "Lua 5.1" and getfenv(func) or bindings._ENV) + return setmetatable(bindings, { __index = env or _G }) + else + return bindings + end +end + +-- Used as a __newindex metamethod to modify variables in cmd_eval(). +local function mutate_bindings(_, name, value) + local FUNC_STACK_OFFSET = 3 -- Stack depth of this function. + local level = stack_inspect_offset + FUNC_STACK_OFFSET + CMD_STACK_LEVEL + + -- Set a local. + do + local i = 1 + repeat + local var = debug.getlocal(level, i) + if name == var then + dbg_writeln( + COLOR_YELLOW + .. "debugger.lua" + .. GREEN_CARET + .. "Set local variable " + .. COLOR_BLUE + .. name + .. COLOR_RESET + ) + return debug.setlocal(level + LUA_JIT_SETLOCAL_WORKAROUND, i, value) + end + i = i + 1 + until var == nil + end + + -- Set an upvalue. + local func = debug.getinfo(level).func + do + local i = 1 + repeat + local var = debug.getupvalue(func, i) + if name == var then + dbg_writeln( + COLOR_YELLOW .. "debugger.lua" .. GREEN_CARET .. "Set upvalue " .. COLOR_BLUE .. name .. COLOR_RESET + ) + return debug.setupvalue(func, i, value) + end + i = i + 1 + until var == nil + end + + -- Set a global. + dbg_writeln( + COLOR_YELLOW .. "debugger.lua" .. GREEN_CARET .. "Set global variable " .. COLOR_BLUE .. name .. COLOR_RESET + ) + _G[name] = value +end + +-- Compile an expression with the given variable bindings. +local function compile_chunk(block, env) + local source = "debugger.lua REPL" + local chunk = nil + + if _VERSION <= "Lua 5.1" then + chunk = loadstring(block, source) + if chunk then + setfenv(chunk, env) + end + else + -- The Lua 5.2 way is a bit cleaner + chunk = load(block, source, "t", env) + end + + if not chunk then + dbg_writeln(COLOR_RED .. "Error: Could not compile block:\n" .. COLOR_RESET .. block) + end + return chunk +end + +local SOURCE_CACHE = {} + +local function where(info, context_lines) + local source = SOURCE_CACHE[info.source] + if not source then + source = {} + local filename = info.source:match "@(.*)" + if filename then + pcall(function() + for line in io.lines(filename) do + table.insert(source, line) + end + end) + elseif info.source then + for line in info.source:gmatch "[^\n]+" do + table.insert(source, line) + end + end + SOURCE_CACHE[info.source] = source + end + + if source and source[info.currentline] then + for i = info.currentline - context_lines, info.currentline + context_lines do + local tab_or_caret = (i == info.currentline and GREEN_CARET or " ") + local line = source[i] + if line then + dbg_writeln(COLOR_GRAY .. "% 4d" .. tab_or_caret .. "%s", i, line) + end + end + else + dbg_writeln(COLOR_RED .. "Error: Source not available for " .. COLOR_BLUE .. info.short_src) + end + + return false +end + +-- Wee version differences +local unpack = unpack or table.unpack +local pack = function(...) + return { n = select("#", ...), ... } +end + +local function cmd_step() + stack_inspect_offset = stack_top + return true, hook_step +end + +local function cmd_next() + stack_inspect_offset = stack_top + return true, hook_next +end + +local function cmd_finish() + local offset = stack_top - stack_inspect_offset + stack_inspect_offset = stack_top + return true, offset < 0 and hook_factory(offset - 1) or hook_finish +end + +local function cmd_print(expr) + local env = local_bindings(1, true) + local chunk = compile_chunk("return " .. expr, env) + if chunk == nil then + return false + end + + -- Call the chunk and collect the results. + local results = pack(pcall(chunk, unpack(rawget(env, "...") or {}))) + + -- The first result is the pcall error. + if not results[1] then + dbg_writeln(COLOR_RED .. "Error:" .. COLOR_RESET .. " " .. results[2]) + else + local output = "" + for i = 2, results.n do + output = output .. (i ~= 2 and ", " or "") .. dbg.pretty(results[i]) + end + + if output == "" then + output = "<no result>" + end + dbg_writeln(COLOR_BLUE .. expr .. GREEN_CARET .. output) + end + + return false +end + +local function cmd_eval(code) + local env = local_bindings(1, true) + local mutable_env = setmetatable({}, { + __index = env, + __newindex = mutate_bindings, + }) + + local chunk = compile_chunk(code, mutable_env) + if chunk == nil then + return false + end + + -- Call the chunk and collect the results. + local success, err = pcall(chunk, unpack(rawget(env, "...") or {})) + if not success then + dbg_writeln(COLOR_RED .. "Error:" .. COLOR_RESET .. " " .. tostring(err)) + end + + return false +end + +local function cmd_down() + local offset = stack_inspect_offset + local info + + repeat -- Find the next frame with a file. + offset = offset + 1 + info = debug.getinfo(offset + CMD_STACK_LEVEL) + until not info or frame_has_line(info) + + if info then + stack_inspect_offset = offset + dbg_writeln("Inspecting frame: " .. format_stack_frame_info(info)) + if tonumber(dbg.auto_where) then + where(info, dbg.auto_where) + end + else + info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL) + dbg_writeln "Already at the bottom of the stack." + end + + return false +end + +local function cmd_up() + local offset = stack_inspect_offset + local info + + repeat -- Find the next frame with a file. + offset = offset - 1 + if offset < stack_top then + info = nil + break + end + info = debug.getinfo(offset + CMD_STACK_LEVEL) + until frame_has_line(info) + + if info then + stack_inspect_offset = offset + dbg_writeln("Inspecting frame: " .. format_stack_frame_info(info)) + if tonumber(dbg.auto_where) then + where(info, dbg.auto_where) + end + else + info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL) + dbg_writeln "Already at the top of the stack." + end + + return false +end + +local function cmd_inspect(offset) + offset = stack_top + tonumber(offset) + local info = debug.getinfo(offset + CMD_STACK_LEVEL) + if info then + stack_inspect_offset = offset + dbg.writeln("Inspecting frame: " .. format_stack_frame_info(info)) + else + dbg.writeln(COLOR_RED .. "ERROR: " .. COLOR_BLUE .. "Invalid stack frame index." .. COLOR_RESET) + end +end + +local function cmd_where(context_lines) + local info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL) + return (info and where(info, tonumber(context_lines) or 5)) +end + +local function cmd_trace() + dbg_writeln("Inspecting frame %d", stack_inspect_offset - stack_top) + local i = 0 + while true do + local info = debug.getinfo(stack_top + CMD_STACK_LEVEL + i) + if not info then + break + end + + local is_current_frame = (i + stack_top == stack_inspect_offset) + local tab_or_caret = (is_current_frame and GREEN_CARET or " ") + dbg_writeln(COLOR_GRAY .. "% 4d" .. COLOR_RESET .. tab_or_caret .. "%s", i, format_stack_frame_info(info)) + i = i + 1 + end + + return false +end + +local function cmd_locals() + local bindings = local_bindings(1, false) + + -- Get all the variable binding names and sort them + local keys = {} + for k, _ in pairs(bindings) do + table.insert(keys, k) + end + table.sort(keys) + + for _, k in ipairs(keys) do + local v = bindings[k] + + -- Skip the debugger object itself, "(*internal)" values, and Lua 5.2's _ENV object. + if not rawequal(v, dbg) and k ~= "_ENV" and not k:match "%(.*%)" then + dbg_writeln(" " .. COLOR_BLUE .. k .. GREEN_CARET .. dbg.pretty(v)) + end + end + + return false +end + +local function cmd_help() + dbg.write( + "" + .. COLOR_BLUE + .. " <return>" + .. GREEN_CARET + .. "re-run last command\n" + .. COLOR_BLUE + .. " c" + .. COLOR_YELLOW + .. "(ontinue)" + .. GREEN_CARET + .. "continue execution\n" + .. COLOR_BLUE + .. " s" + .. COLOR_YELLOW + .. "(tep)" + .. GREEN_CARET + .. "step forward by one line (into functions)\n" + .. COLOR_BLUE + .. " n" + .. COLOR_YELLOW + .. "(ext)" + .. GREEN_CARET + .. "step forward by one line (skipping over functions)\n" + .. COLOR_BLUE + .. " f" + .. COLOR_YELLOW + .. "(inish)" + .. GREEN_CARET + .. "step forward until exiting the current function\n" + .. COLOR_BLUE + .. " u" + .. COLOR_YELLOW + .. "(p)" + .. GREEN_CARET + .. "move up the stack by one frame\n" + .. COLOR_BLUE + .. " d" + .. COLOR_YELLOW + .. "(own)" + .. GREEN_CARET + .. "move down the stack by one frame\n" + .. COLOR_BLUE + .. " i" + .. COLOR_YELLOW + .. "(nspect) " + .. COLOR_BLUE + .. "[index]" + .. GREEN_CARET + .. "move to a specific stack frame\n" + .. COLOR_BLUE + .. " w" + .. COLOR_YELLOW + .. "(here) " + .. COLOR_BLUE + .. "[line count]" + .. GREEN_CARET + .. "print source code around the current line\n" + .. COLOR_BLUE + .. " e" + .. COLOR_YELLOW + .. "(val) " + .. COLOR_BLUE + .. "[statement]" + .. GREEN_CARET + .. "execute the statement\n" + .. COLOR_BLUE + .. " p" + .. COLOR_YELLOW + .. "(rint) " + .. COLOR_BLUE + .. "[expression]" + .. GREEN_CARET + .. "execute the expression and print the result\n" + .. COLOR_BLUE + .. " t" + .. COLOR_YELLOW + .. "(race)" + .. GREEN_CARET + .. "print the stack trace\n" + .. COLOR_BLUE + .. " l" + .. COLOR_YELLOW + .. "(ocals)" + .. GREEN_CARET + .. "print the function arguments, locals and upvalues.\n" + .. COLOR_BLUE + .. " h" + .. COLOR_YELLOW + .. "(elp)" + .. GREEN_CARET + .. "print this message\n" + .. COLOR_BLUE + .. " q" + .. COLOR_YELLOW + .. "(uit)" + .. GREEN_CARET + .. "halt execution\n" + ) + return false +end + +local last_cmd = false + +local commands = { + ["^c$"] = function() + return true + end, + ["^s$"] = cmd_step, + ["^n$"] = cmd_next, + ["^f$"] = cmd_finish, + ["^p%s+(.*)$"] = cmd_print, + ["^e%s+(.*)$"] = cmd_eval, + ["^u$"] = cmd_up, + ["^d$"] = cmd_down, + ["i%s*(%d+)"] = cmd_inspect, + ["^w%s*(%d*)$"] = cmd_where, + ["^t$"] = cmd_trace, + ["^l$"] = cmd_locals, + ["^h$"] = cmd_help, + ["^q$"] = function() + dbg.exit(0) + return true + end, +} + +local function match_command(line) + for pat, func in pairs(commands) do + -- Return the matching command and capture argument. + if line:find(pat) then + return func, line:match(pat) + end + end +end + +-- Run a command line +-- Returns true if the REPL should exit and the hook function factory +local function run_command(line) + -- GDB/LLDB exit on ctrl-d + if line == nil then + dbg.exit(1) + return true + end + + -- Re-execute the last command if you press return. + if line == "" then + line = last_cmd or "h" + end + + local command, command_arg = match_command(line) + if command then + last_cmd = line + -- unpack({...}) prevents tail call elimination so the stack frame indices are predictable. + return unpack { command(command_arg) } + elseif dbg.auto_eval then + return unpack { cmd_eval(line) } + else + dbg_writeln( + COLOR_RED + .. "Error:" + .. COLOR_RESET + .. " command '%s' not recognized.\nType 'h' and press return for a command list.", + line + ) + return false + end +end + +repl = function(reason) + -- Skip frames without source info. + while not frame_has_line(debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL - 3)) do + stack_inspect_offset = stack_inspect_offset + 1 + end + + local info = debug.getinfo(stack_inspect_offset + CMD_STACK_LEVEL - 3) + reason = reason and (COLOR_YELLOW .. "break via " .. COLOR_RED .. reason .. GREEN_CARET) or "" + dbg_writeln(reason .. format_stack_frame_info(info)) + + if tonumber(dbg.auto_where) then + where(info, dbg.auto_where) + end + + repeat + local success, done, hook = pcall(run_command, dbg.read(COLOR_RED .. "debugger.lua> " .. COLOR_RESET)) + if success then + debug.sethook(hook and hook(0), "crl") + else + local message = COLOR_RED .. "INTERNAL DEBUGGER.LUA ERROR. ABORTING\n:" .. COLOR_RESET .. " " .. done + dbg_writeln(message) + error(message) + end + until done +end + +-- Make the debugger object callable like a function. +dbg = setmetatable({}, { + __call = function(_, condition, top_offset, source) + if condition then + return + end + + top_offset = (top_offset or 0) + stack_inspect_offset = top_offset + stack_top = top_offset + + debug.sethook(hook_next(1, source or "dbg()"), "crl") + return + end, +}) + +-- Expose the debugger's IO functions. +dbg.read = dbg_read +dbg.write = dbg_write +dbg.shorten_path = function(path) + return path +end +dbg.exit = function(err) + os.exit(err) +end + +dbg.writeln = dbg_writeln + +dbg.pretty_depth = 3 +dbg.pretty = pretty +dbg.pp = function(value, depth) + dbg_writeln(dbg.pretty(value, depth)) +end + +dbg.auto_where = false +dbg.auto_eval = false + +local lua_error, lua_assert = error, assert + +-- Works like error(), but invokes the debugger. +function dbg.error(err, level) + level = level or 1 + dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. dbg.pretty(err)) + dbg(false, level, "dbg.error()") + + lua_error(err, level) +end + +-- Works like assert(), but invokes the debugger on a failure. +function dbg.assert(condition, message) + message = message or "assertion failed!" + if not condition then + dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. message) + dbg(false, 1, "dbg.assert()") + end + + return lua_assert(condition, message) +end + +-- Works like pcall(), but invokes the debugger on an error. +function dbg.call(f, ...) + return xpcall(f, function(err) + dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. dbg.pretty(err)) + dbg(false, 1, "dbg.call()") + + return err + end, ...) +end + +-- Error message handler that can be used with lua_pcall(). +function dbg.msgh(...) + if debug.getinfo(2) then + dbg_writeln(COLOR_RED .. "ERROR: " .. COLOR_RESET .. dbg.pretty(...)) + dbg(false, 1, "dbg.msgh()") + else + dbg_writeln( + COLOR_RED + .. "debugger.lua: " + .. COLOR_RESET + .. "Error did not occur in Lua code. Execution will continue after dbg_pcall()." + ) + end + + return ... +end + +-- Assume stdin/out are TTYs unless we can use LuaJIT's FFI to properly check them. +local stdin_isatty = true +local stdout_isatty = true + +-- Conditionally enable the LuaJIT FFI. +local ffi = (jit and require "ffi") +if ffi then + ffi.cdef [[ + int isatty(int); // Unix + int _isatty(int); // Windows + void free(void *ptr); + + char *readline(const char *); + int add_history(const char *); + ]] + + local function get_func_or_nil(sym) + local success, func = pcall(function() + return ffi.C[sym] + end) + return success and func or nil + end + + local isatty = get_func_or_nil "isatty" or get_func_or_nil "_isatty" or (ffi.load "ucrtbase")["_isatty"] + stdin_isatty = isatty(0) + stdout_isatty = isatty(1) +end + +-- Conditionally enable color support. +local color_maybe_supported = (stdout_isatty and os.getenv "TERM" and os.getenv "TERM" ~= "dumb") +if color_maybe_supported and not os.getenv "DBG_NOCOLOR" then + COLOR_GRAY = string.char(27) .. "[90m" + COLOR_RED = string.char(27) .. "[91m" + COLOR_BLUE = string.char(27) .. "[94m" + COLOR_YELLOW = string.char(27) .. "[33m" + COLOR_RESET = string.char(27) .. "[0m" + GREEN_CARET = string.char(27) .. "[92m => " .. COLOR_RESET +end + +if stdin_isatty and not os.getenv "DBG_NOREADLINE" then + pcall(function() + local linenoise = require "linenoise" + + -- Load command history from ~/.lua_history + local hist_path = os.getenv "HOME" .. "/.lua_history" + linenoise.historyload(hist_path) + linenoise.historysetmaxlen(50) + + local function autocomplete(env, input, matches) + for name, _ in pairs(env) do + if name:match("^" .. input .. ".*") then + linenoise.addcompletion(matches, name) + end + end + end + + -- Auto-completion for locals and globals + linenoise.setcompletion(function(matches, input) + -- First, check the locals and upvalues. + local env = local_bindings(1, true) + autocomplete(env, input, matches) + + -- Then, check the implicit environment. + env = getmetatable(env).__index + autocomplete(env, input, matches) + end) + + dbg.read = function(prompt) + local str = linenoise.linenoise(prompt) + if str and not str:match "^%s*$" then + linenoise.historyadd(str) + linenoise.historysave(hist_path) + end + return str + end + dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Linenoise support enabled.") + end) + + -- Conditionally enable LuaJIT readline support. + pcall(function() + if dbg.read == dbg_read and ffi then + local readline = ffi.load "readline" + dbg.read = function(prompt) + local cstr = readline.readline(prompt) + if cstr ~= nil then + local str = ffi.string(cstr) + if string.match(str, "[^%s]+") then + readline.add_history(cstr) + end + + ffi.C.free(cstr) + return str + else + return nil + end + end + dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Readline support enabled.") + end + end) +end + +-- Detect Lua version. +if jit then -- LuaJIT + LUA_JIT_SETLOCAL_WORKAROUND = -1 + dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Loaded for " .. jit.version) +elseif "Lua 5.1" <= _VERSION and _VERSION <= "Lua 5.4" then + dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Loaded for " .. _VERSION) +else + dbg_writeln(COLOR_YELLOW .. "debugger.lua: " .. COLOR_RESET .. "Not tested against " .. _VERSION) + dbg_writeln "Please send me feedback!" +end + +return dbg diff --git a/.config/awesome/quarrel/delegate.lua b/.config/awesome/quarrel/delegate.lua index 54db786..3ea6d75 100644 --- a/.config/awesome/quarrel/delegate.lua +++ b/.config/awesome/quarrel/delegate.lua @@ -1,8 +1,10 @@ +local M = {} + --- Capture `fn`'s upvalues and pass to `delegate` ---@param delegate fun(env: table<string, any>, _: ...): ... ---@param fn function ---@return fun(...): ... -return function(delegate, fn) +function M.new(delegate, fn) local upvalues = {} for i = 1, debug.getinfo(fn, "u").nups do local name, value = debug.getupvalue(fn, i) @@ -12,3 +14,9 @@ return function(delegate, fn) return delegate(upvalues, ...) end end + +return setmetatable(M, { + __call = function(_, ...) + return M.new(...) + end, +}) diff --git a/.config/awesome/quarrel/fs.lua b/.config/awesome/quarrel/fs.lua index 502f189..fa3fc21 100644 --- a/.config/awesome/quarrel/fs.lua +++ b/.config/awesome/quarrel/fs.lua @@ -1,24 +1,35 @@ +local gdebug = require "gears.debug" local GFile = require("lgi").Gio.File ---@class QuarrelFs -local qfs = {} +local M = {} --- Read a file with the specified format (or "a") and close the file ---@param path string ----@param format openmode +---@param format? openmode ---@return any -function qfs.read(path, format) +function M.read(path, format) local f = assert(io.open(path, "r")) local c = f:read(format or "a") f:close() return c end +--- Write a file with the specified format (or "a") and close the file +---@param path string +---@param format? openmode +---@return any +function M.write(content, path, format) + local f = assert(io.open(path, format or "w+")) + f:write(content) + f:close() +end + --- List files in a directory ---@param path string ---@param absolute boolean? ---@return table -function qfs.ls_files(path, absolute) +function M.ls_files(path, absolute) local files = GFile.new_for_path(path):enumerate_children("standard::*", 0) local files_filtered = {} @@ -41,4 +52,4 @@ function qfs.ls_files(path, absolute) return files_filtered end -return qfs +return M diff --git a/.config/awesome/quarrel/iconset.lua b/.config/awesome/quarrel/iconset.lua new file mode 100644 index 0000000..43b7104 --- /dev/null +++ b/.config/awesome/quarrel/iconset.lua @@ -0,0 +1,21 @@ +local gstring = require "gears.string" + +local M = {} + +--- Generate a shim to allow indexing an iconset as a Lua table +---@param path string +---@return table +function M.new(path) + return setmetatable({}, { + ---@param icon string + __index = function(self, icon) + return path .. (gstring.endswith(path, "/") and "" or "/") .. icon:gsub("_", "-") .. ".svg" + end, + }) +end + +return setmetatable(M, { + __call = function(_, ...) + return M.new(...) + end, +}) diff --git a/.config/awesome/quarrel/init.lua b/.config/awesome/quarrel/init.lua index 96f7f5b..5617c1a 100644 --- a/.config/awesome/quarrel/init.lua +++ b/.config/awesome/quarrel/init.lua @@ -9,14 +9,4 @@ function quarrel.debug(message) n { message = tostring(message) } end ---- Check if there was a restart ----@return boolean -function quarrel.is_restart() - awesome.register_xproperty("is_restart", "boolean") - local restart_detected = awesome.get_xproperty "is_restart" ~= nil - awesome.set_xproperty("is_restart", true) - - return restart_detected -end - return quarrel diff --git a/.config/awesome/quarrel/lua-rust.tar.gz b/.config/awesome/quarrel/lua-rust.tar.gz Binary files differdeleted file mode 100644 index 5ff33fd..0000000 --- a/.config/awesome/quarrel/lua-rust.tar.gz +++ /dev/null diff --git a/.config/awesome/quarrel/markup.lua b/.config/awesome/quarrel/markup.lua index d206530..f89368c 100644 --- a/.config/awesome/quarrel/markup.lua +++ b/.config/awesome/quarrel/markup.lua @@ -1,8 +1,10 @@ +local M = {} + --- Apply markup to a file ---@param content string ---@param args { bold: boolean, italic: boolean, fg: string, bg: string } ---@return string -return function(content, args) +function M.convert(content, args) args = args or {} if args.bold then content = "<b>" .. content .. "</b>" @@ -28,3 +30,9 @@ return function(content, args) return content end + +return setmetatable(M, { + __call = function(_, ...) + return M.convert(...) + end, +}) diff --git a/.config/awesome/quarrel/math/consts.lua b/.config/awesome/quarrel/math/consts.lua new file mode 100644 index 0000000..f18a093 --- /dev/null +++ b/.config/awesome/quarrel/math/consts.lua @@ -0,0 +1,5 @@ +local C = {} + +C.EPSILON = 2 ^ -52 + +return C diff --git a/.config/awesome/quarrel/math.lua b/.config/awesome/quarrel/math/init.lua index b16547b..d437654 100644 --- a/.config/awesome/quarrel/math.lua +++ b/.config/awesome/quarrel/math/init.lua @@ -1,11 +1,9 @@ +local M = require "quarrel.math.consts" local gmath = require "gears.math" ----@class QuarrelMath -local qmath = {} - -- TODO: Finish documenting these functions -function qmath.step_value(value, steps) +function M.step_value(value, steps) if value > steps[#steps - 1][1] then return steps[#steps - 1][2] end @@ -16,16 +14,16 @@ function qmath.step_value(value, steps) end end -function qmath.translate_range(value, in_min, in_max, out_min, out_max) +function M.translate_range(value, in_min, in_max, out_min, out_max) return out_min + ((out_max - out_min) / (in_max - in_min)) * (value - in_min) end -function qmath.percentage(value, max) +function M.percentage(value, max) return gmath.round(value / max * 100) end -function qmath.clamp(value, min, max) +function M.clamp(value, min, max) return math.max(math.min(value, max), min) end -return qmath +return M diff --git a/.config/awesome/quarrel/native/Cargo.toml b/.config/awesome/quarrel/native/Cargo.toml index 8d56c9f..2551507 100644 --- a/.config/awesome/quarrel/native/Cargo.toml +++ b/.config/awesome/quarrel/native/Cargo.toml @@ -8,22 +8,21 @@ edition = "2021" [dependencies] freedesktop_entry_parser = "1.3.0" cpc = "1.9.1" -mlua = { version = "0.8.7", features = [ "module", "lua54", "serialize" ] } +mlua = { version = "0.10.3", features = [ "module", "lua54", "serialize" ] } rayon = "1.6.1" serde = { version = "1.0.152", features = [ "derive" ] } url = "2.3.1" -rodio = "0.17.1" -nix = "0.26.2" +rodio = "0.20.1" +nix = { version = "0.29.0", features = [ "ioctl", "socket" ] } chrono = "0.4.24" -itertools = "0.10.5" +itertools = "0.14.0" html-escape = "0.2.13" -mpd = { git = "https://github.com/kstep/rust-mpd", features = [ "serde" ], version = "0.1.0" } -cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core.git", version = "0.18.0" } -gdk-pixbuf = { git = "https://github.com/gtk-rs/gtk-rs-core.git", version = "0.18.0" } -symphonia = "0.5.3" -dirs = "5.0.1" +dirs = "6.0.0" once_cell = "1.18.0" - +ureq = "3.0.9" +anyhow = { version = "1.0.97", features = ["backtrace"] } +mdrop = { git = "https://github.com/frahz/mdrop" } +sysinfo = "0.35.0" [lib] crate-type = ["cdylib"] diff --git a/.config/awesome/quarrel/native/init.lua b/.config/awesome/quarrel/native/init.lua index e5d5aab..6a823ba 100644 --- a/.config/awesome/quarrel/native/init.lua +++ b/.config/awesome/quarrel/native/init.lua @@ -26,16 +26,12 @@ package.cpath = package.cpath .. ";" .. cfg .. "quarrel/native/lib?.so" ---@field decode_html fun(input: string): string ---@field open_file fun(path: string): FileHandle ----@class Mpd ----@field init - ---@class Net ---@field get_essid fun(): string ---@class QuarrelNative ---@field lenses Lenses ---@field util Util ----@field mpd Mpd ---@field net Net local qnative = require "qnative" diff --git a/.config/awesome/quarrel/native/src/http.rs b/.config/awesome/quarrel/native/src/http.rs new file mode 100644 index 0000000..e995556 --- /dev/null +++ b/.config/awesome/quarrel/native/src/http.rs @@ -0,0 +1,22 @@ +use ureq::{Agent, agent}; +use std::{sync::LazyLock, thread}; +use mlua::prelude::*; + +static AGENT: LazyLock<Agent> = LazyLock::new(|| agent()); + +struct Stream(ureq::Body); + +pub fn get(_: &Lua, url: String, callback: LuaFunction, err_callback: LuaFunction) -> LuaResult<()> { + thread::spawn(|| { + match AGENT.get(url).call() { + Ok(body) => { + + }, + Err(err) => { + err_callback.call(err.to_string()); + } + } + // callback.call::<>() + }); + Ok(()) +} diff --git a/.config/awesome/quarrel/native/src/lenses/application.rs b/.config/awesome/quarrel/native/src/lenses/application.rs index 72aba8d..89b7bb4 100644 --- a/.config/awesome/quarrel/native/src/lenses/application.rs +++ b/.config/awesome/quarrel/native/src/lenses/application.rs @@ -1,6 +1,5 @@ use std::{ - fs::read_dir, - path::PathBuf, + fs::read_dir, path::PathBuf }; use freedesktop_entry_parser as fd; @@ -8,12 +7,54 @@ use mlua::prelude::*; use rayon::prelude::*; use url::Url; -use crate::lenses::entry::{ - entries_to_lua_table, +use crate::lenses::{ Entry, + Lense, + Cache }; -fn parse_entry(entry: &fd::Entry, path: &PathBuf) -> Result<Entry, ()> { +#[derive(Default)] +pub struct Application(pub Cache); + +impl Lense for Application { + const NAME: &str = "Application"; + + fn get_cache(&self) -> &Cache { + &self.0 + } + + fn set_cache(&mut self, cache: Cache) { + self.0 = cache; + } + + fn query(_: &Lua, input: String) -> Result<Vec<Entry>, anyhow::Error> { + let applications_dir = "/usr/share/applications"; + let entries = read_dir(applications_dir)? + .map(|result| result.map(|e| e.path())) + .collect::<Result<Vec<_>, std::io::Error>>()?; + + let parsed_entries: Vec<Entry> = entries + .into_par_iter() + .filter(|path| path.extension().is_some_and(|ext| ext == "desktop")) + .filter_map(|path| { + parse_entry(path).ok().flatten() + }) + .collect(); + + Ok( + parsed_entries + .into_iter() + .filter(|entry| entry.message.to_lowercase().contains(&input.to_lowercase())) + .collect(), + ) + } +} + +fn parse_entry(path: PathBuf) -> Result<Option<Entry>, ()> { + let Ok(entry) = fd::parse_entry(&path) else { + return Err(()) + }; + let section = entry.section("Desktop Entry"); let name = section.attr("Name").ok_or(())?.to_string(); @@ -38,14 +79,14 @@ fn parse_entry(entry: &fd::Entry, path: &PathBuf) -> Result<Entry, ()> { } } 'c' => new_exec.replace_range(index..index + 2, &name), - 'k' => new_exec.replace_range(index..index + 2, Url::from_file_path(path)?.as_str()), + 'k' => new_exec.replace_range(index..index + 2, Url::from_file_path(&path)?.as_str()), 'f' | 'u' | 'v' | 'm' | 'd' | 'n' => new_exec.replace_range(index..index + 2, ""), _ => continue, } } - Ok(Entry { + Ok(Some(Entry { message: name, exec: Some(( new_exec, @@ -55,33 +96,5 @@ fn parse_entry(entry: &fd::Entry, path: &PathBuf) -> Result<Entry, ()> { .parse() .map_err(drop)?, )), - provider: "Application".to_string(), - }) -} - -pub fn query(lua: &Lua, input: String) -> LuaResult<LuaTable> { - let applications_dir = "/usr/share/applications"; - let entries = read_dir(applications_dir)? - .map(|result| result.map(|e| e.path())) - .collect::<Result<Vec<_>, std::io::Error>>()?; - - let parsed_entries: Vec<Entry> = entries - .into_par_iter() - .filter(|path| matches!(path.extension(), Some(ext) if ext == "desktop")) - .filter_map(|path| { - let Ok(entry) = fd::parse_entry(&path) else { - return None - }; - - parse_entry(&entry, &path).ok() - }) - .collect(); - - Ok(entries_to_lua_table( - parsed_entries - .into_iter() - .filter(|entry| entry.message.to_lowercase().contains(&input.to_lowercase())) - .collect(), - lua, - )) + })) } diff --git a/.config/awesome/quarrel/native/src/lenses/calculator.rs b/.config/awesome/quarrel/native/src/lenses/calculator.rs index 07f1ee2..640bdeb 100644 --- a/.config/awesome/quarrel/native/src/lenses/calculator.rs +++ b/.config/awesome/quarrel/native/src/lenses/calculator.rs @@ -5,25 +5,36 @@ use cpc::{ }; use mlua::prelude::*; -use crate::lenses::entry::{ - entries_to_lua_table, +use crate::lenses::{ Entry, + Cache, + Lense }; -pub fn query(lua: &Lua, input: String) -> LuaResult<LuaTable> { - let result = match eval(input.trim(), true, Unit::Celsius, false) { - Ok(result) => { - format!("{result}") - } - Err(_) => return lua.create_table(), - }; - - Ok(entries_to_lua_table( - vec![Entry { - message: result, - exec: None, - provider: "Calculator".to_string(), - }], - lua, - )) +pub struct Calculator; + +impl Lense for Calculator { + const NAME: &str = "Calculator"; + + fn get_cache(&self) -> &Cache { + &Cache::Stale + } + + fn set_cache(&mut self, _: Cache) {} + + fn query(_: &Lua, input: String) -> Result<Vec<Entry>, anyhow::Error> { + let result = match eval(input.trim(), true, Unit::Celsius, false) { + Ok(result) => { + format!("{result}") + } + Err(err) => { return Err(anyhow::anyhow!(err)); }, + }; + + Ok(vec![Entry { + message: result, + exec: None, + }; 1] + ) + } } + diff --git a/.config/awesome/quarrel/native/src/lenses/entry.rs b/.config/awesome/quarrel/native/src/lenses/entry.rs deleted file mode 100644 index c7ac09e..0000000 --- a/.config/awesome/quarrel/native/src/lenses/entry.rs +++ /dev/null @@ -1,19 +0,0 @@ -use mlua::{ - prelude::*, - LuaSerdeExt, -}; -use serde::Serialize; - -#[derive(Serialize, Clone)] -pub struct Entry { - pub message: String, - pub exec: Option<(String, bool)>, - pub provider: String, -} - -pub fn entries_to_lua_table(entries: Vec<Entry>, lua: &Lua) -> LuaTable { - match lua.to_value(&entries).unwrap() { - LuaValue::Table(t) => t, - _ => unreachable!(), - } -} diff --git a/.config/awesome/quarrel/native/src/lenses/mod.rs b/.config/awesome/quarrel/native/src/lenses/mod.rs index d0db6f7..6eb5b58 100644 --- a/.config/awesome/quarrel/native/src/lenses/mod.rs +++ b/.config/awesome/quarrel/native/src/lenses/mod.rs @@ -1,3 +1,79 @@ pub mod application; pub mod calculator; -pub mod entry; + +use mlua::{ + prelude::*, + LuaSerdeExt, +}; +use serde::{Serialize, Deserialize}; + +#[derive(Deserialize, Serialize, Clone)] +pub struct Entry { + pub message: String, + pub exec: Option<(String, bool)>, +} + +impl IntoLua for Entry { + fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> { + return lua.to_value(&self) + } +} + +impl FromLua for Entry { + fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> { + return lua.from_value(value); + } +} + +#[derive(Default)] +pub enum Cache { + Valid(Vec<Entry>), + #[default] + Stale +} + +pub struct _Lense<T: Lense>(pub T); + +pub trait Lense { + const NAME: &'static str; + + fn set_cache(&mut self, cache: Cache); + fn get_cache(&self) -> &Cache; + + fn query(lua: &Lua, input: String) -> Result<Vec<Entry>, anyhow::Error>; +} + +impl<T: Lense + 'static> LuaUserData for _Lense<T> { + fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) { + // fields.add_field_method_get("cache", |lua, this| { + // let cache = this.0.get_cache(); + // match cache { + // Cache::Valid(cache) => Ok(cache.clone().into_lua(lua)?), + // Cache::Stale => Ok(LuaNil) + // } + // }); + + // fields.add_field_method_set("cache", |_, this, cache: Vec<Entry>| { + // Ok(this.0.set_cache(Cache::Valid(cache))) + // }); + + fields.add_field_method_get("stale", |_, this| Ok(matches!(this.0.get_cache(), Cache::Stale))); + + fields.add_field("name", T::NAME); + } + + fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) { + methods.add_method_mut("query", |lua, this, input: String| { + return Ok(match this.0.get_cache() { + Cache::Valid(entries) => entries.clone(), + Cache::Stale => { + let entries = T::query(lua, input).map_err(LuaError::external)?; + this.0.set_cache(Cache::Valid(entries.clone())); + entries + } + }); + }); + + methods.add_method_mut("mark_stale", |_, this, _: ()| Ok(this.0.set_cache(Cache::Stale)) ); + } +} diff --git a/.config/awesome/quarrel/native/src/lib.rs b/.config/awesome/quarrel/native/src/lib.rs index 472313e..4f1780d 100644 --- a/.config/awesome/quarrel/native/src/lib.rs +++ b/.config/awesome/quarrel/native/src/lib.rs @@ -1,29 +1,29 @@ mod lenses; -mod mpd; mod net; +// mod http; mod util; +mod moondrop; use mlua::prelude::*; +use lenses::{ _Lense }; #[mlua::lua_module] fn qnative(lua: &Lua) -> LuaResult<LuaTable> { let lenses = lua.create_table()?; - lenses.set("1", lua.create_function(lenses::calculator::query)?)?; - lenses.set("2", lua.create_function(lenses::application::query)?)?; + lenses.push(_Lense(lenses::calculator::Calculator))?; + lenses.push(_Lense(lenses::application::Application::default()))?; + // lenses.set("1", lua.create_function(lenses::calculator::Application::query)?)?; + // lenses.set("2", lua.create_function(lenses::application::Application::query)?)?; let util = lua.create_table()?; util.set("decode_html", lua.create_function(util::decode_html)?)?; util.set("open_file", lua.create_function(util::FileHandle::new)?)?; - let mpd = lua.create_table()?; - mpd.set("init", lua.create_function(mpd::init)?)?; - let net = lua.create_table()?; net.set("get_essid", lua.create_function(net::get_first_essid)?)?; let exports = lua.create_table()?; exports.set("lenses", lenses)?; - exports.set("mpd", mpd)?; exports.set("net", net)?; exports.set("util", util)?; diff --git a/.config/awesome/quarrel/native/src/moondrop.rs b/.config/awesome/quarrel/native/src/moondrop.rs new file mode 100644 index 0000000..d416ef6 --- /dev/null +++ b/.config/awesome/quarrel/native/src/moondrop.rs @@ -0,0 +1,3 @@ +use mdrop::Moondrop; + + diff --git a/.config/awesome/quarrel/native/src/mpd.rs b/.config/awesome/quarrel/native/src/mpd.rs deleted file mode 100644 index 08c9e42..0000000 --- a/.config/awesome/quarrel/native/src/mpd.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::{ - ffi::c_void, - fs::File, - net::TcpStream, - path::PathBuf, - sync::mpsc::channel, -}; - -use dirs::home_dir; -use gdk_pixbuf::{ - ffi::GdkPixbuf, - glib::translate::IntoGlibPtr, - traits::PixbufLoaderExt, - Pixbuf, - PixbufLoader, -}; -use mlua::{ - prelude::*, - LuaSerdeExt, -}; -use mpd::Client; -use once_cell::sync::Lazy; -use symphonia::{ - core::{ - formats::FormatOptions, - io::{ - MediaSourceStream, - MediaSourceStreamOptions, - }, - meta::MetadataOptions, - probe::Hint, - }, - default::get_probe, -}; - -static MPD_MUSIC_PATH: Lazy<PathBuf> = Lazy::new(|| { - [ - home_dir().expect("home directory should be set"), - "Music".into(), - ] - .iter() - .collect() -}); - -static COVER_FORMATS: [&str; 2] = ["png", "jpg"]; - -pub struct Connection(Client<TcpStream>); - -impl LuaUserData for Connection { - fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method_mut("status", |lua, this: &mut Connection, (): ()| { - lua.to_value(&this.0.status().map_err(LuaError::external)?) - }); - - methods.add_method_mut("song", |lua, this: &mut Connection, (): ()| { - Ok( - if let Some(song) = this.0.currentsong().map_err(LuaError::external)? { - lua.to_value(&song)? - } else { - LuaNil - }, - ) - }); - - methods.add_method( - "get_cover_pixbuf", - |_, _: &Connection, file: String| { - let song_path = MPD_MUSIC_PATH.join(file); - let mut has_external_cover = false; - let mut cover_path = PathBuf::new(); - - for format in COVER_FORMATS { - let cover = song_path - .parent() - .unwrap() - .to_owned() - .join(format!("cover.{}", format)); - if cover.exists() { - has_external_cover = cover.exists(); - cover_path = cover; - break; - } - } - - let mss = MediaSourceStream::new( - Box::new(File::open(song_path)?), - MediaSourceStreamOptions::default(), - ); - - let mut probed = get_probe() - .format( - &Hint::default(), - mss, - &FormatOptions::default(), - &MetadataOptions::default(), - ) - .map_err(LuaError::external)?; - - let visuals; - - if let Some(metadata) = probed.format.metadata().skip_to_latest() { - visuals = metadata.visuals(); - if visuals.is_empty() && has_external_cover { - let pixbuf = Pixbuf::from_file(cover_path).map_err(LuaError::external)?; - - return Ok(( - Some(LuaLightUserData(unsafe { - <Pixbuf as IntoGlibPtr<*mut GdkPixbuf>>::into_glib_ptr(pixbuf) - .cast::<c_void>() - })), - Some(true), - )); - } - - let loader = PixbufLoader::new(); - loader - .write(visuals.first().unwrap().data.as_ref()) - .map_err(LuaError::external)?; - loader.close().map_err(LuaError::external)?; - - return Ok(( - Some(LuaLightUserData(unsafe { - <Pixbuf as IntoGlibPtr<*mut GdkPixbuf>>::into_glib_ptr( - loader.pixbuf().expect("Pixbuf should be initialized"), - ) - .cast::<c_void>() - })), - Some(false), - )); - } - - Ok((None, None)) - }, - ); - } -} - -pub fn init(_: &Lua, _: ()) -> LuaResult<Connection> { - Ok(Connection( - Client::connect("localhost:6600").map_err(LuaError::external)?, - )) -} diff --git a/.config/awesome/quarrel/native/src/net/mod.rs b/.config/awesome/quarrel/native/src/net/mod.rs index 96c853e..dbd87b9 100644 --- a/.config/awesome/quarrel/native/src/net/mod.rs +++ b/.config/awesome/quarrel/native/src/net/mod.rs @@ -9,7 +9,7 @@ use std::{ c_void, }, mem::size_of, - os::fd::RawFd, + os::fd::{IntoRawFd, RawFd}, }; use mlua::prelude::*; @@ -108,7 +108,7 @@ fn get_first_socket() -> LuaResult<RawFd> { for family in families { if let Ok(socket) = open_socket(family, SockType::Datagram, SockFlag::empty(), None) { - return Ok(socket); + return Ok(socket.into_raw_fd()); } } diff --git a/.config/awesome/quarrel/native/src/util.rs b/.config/awesome/quarrel/native/src/util.rs index 85a2574..41096d8 100644 --- a/.config/awesome/quarrel/native/src/util.rs +++ b/.config/awesome/quarrel/native/src/util.rs @@ -1,7 +1,6 @@ use std::{ cell::RefCell, fs::File, - fs::OpenOptions, io::{ Read, Seek, @@ -49,7 +48,7 @@ enum StringOrNumber { pub struct FileHandle(Rc<RefCell<File>>); impl LuaUserData for FileHandle { - fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods<'lua, M: LuaUserDataMethods<Self>>(methods: &mut M) { methods.add_method("read", |lua, this: &FileHandle, mode: String| { let content = this.read()?; Ok(lua.to_value(&match ReadMode::from_str(&mode).unwrap() { diff --git a/.config/awesome/quarrel/persistent.lua b/.config/awesome/quarrel/persistent.lua new file mode 100644 index 0000000..2b15c89 --- /dev/null +++ b/.config/awesome/quarrel/persistent.lua @@ -0,0 +1,62 @@ +local gdebug = require "gears.debug" +local qfs = require "quarrel.fs" +local qjson = require "quarrel.json" + +awesome.register_xproperty("is_restart", "boolean") + +---@alias QPersistentValue string|number|table|nil|boolean + +---@class QuarrelPersistent +local qpersistent = {} + +---@type table<string, QPersistentValue> +local inner = {} + +--- Store a key-value pair in a persistent store +---@param key string +---@param value QPersistentValue +function qpersistent.store(key, value) + inner[key] = value +end + +--- Get a key-value pair in a persistent store +---@param key string +---@return QPersistentValue +function qpersistent.get(key) + return inner[key] +end + +--- Check if there was a restart +---@return boolean +function qpersistent.is_restart() + return awesome.get_xproperty "is_restart" ~= nil +end + +do + local ok, content = pcall(qfs.read, "/tmp/qpersistent-store") + + if not ok and not qpersistent.is_restart() then + gdebug.print_error "failed to read persistent store" + inner = {} + else + inner = qjson.decode(content) + end +end + +awesome.connect_signal("exit", function(restart) + if not restart then + return + end -- we probably errored + + local ok = pcall(qfs.write, qjson.encode(inner), "/tmp/qpersistent-store") + if not ok then + gdebug.print_error "failed to open persistent store" + return + end + + if not qpersistent.is_restart() then + awesome.set_xproperty("is_restart", true) + end +end) + +return qpersistent diff --git a/.config/awesome/quarrel/store.lua b/.config/awesome/quarrel/store.lua deleted file mode 100644 index 9422c21..0000000 --- a/.config/awesome/quarrel/store.lua +++ /dev/null @@ -1,4 +0,0 @@ ----@type table -local qstore = {} - -return qstore diff --git a/.config/awesome/quarrel/ui.lua b/.config/awesome/quarrel/ui.lua deleted file mode 100644 index 1db4a70..0000000 --- a/.config/awesome/quarrel/ui.lua +++ /dev/null @@ -1,145 +0,0 @@ -local awful = require "awful" -local gtable = require "gears.table" -local qbind = require "quarrel.bind" -local qvars = require "quarrel.vars" -local rtimed = require "lib.rubato.timed" -local wibox = require "wibox" - ----@class QuarrelUi -local qui = {} - ---- Return qvars.text_font with size scaled by factor ----@param factor number ----@return string ----@see QuarrelVars.text_font -function qui.font(factor) - return qvars.text_font .. " " .. qvars.font_size * (factor or 1) -end - ---- Inject background widget styling into target ----@param target table ----@return table -function qui.styled(target) - return gtable.crush({ - bg = qvars.colors.bg, - border_color = qvars.colors.bright.black, - border_width = qvars.border_width, - shape = qvars.shape, - }, target) -end - ---- Generate a styled popup ----@param target table ----@return table -function qui.popup(target) - target.widget = { - widget = wibox.container.margin, - margins = qvars.big_padding, - target.widget, - } - - return awful.popup(qui.styled(target)) -end - ---- Generate svg recolor string ----@param color string ----@return string -function qui.recolor(color) - return "svg{fill:" .. color .. "}" -end - ---- Generate icon widget ----@param args table ----@return table -function qui.icon(args) - return gtable.crush({ - widget = wibox.widget.imagebox, - image = args.icon, - forced_width = qvars.char_height, - forced_height = qvars.char_height, - stylesheet = qui.recolor(args.color or qvars.colors.fg), - }, args.widget or {}) -end - ---- Generate button widget ----@param args table ----@return table -function qui.button(args) - args.press = args.press or function(_) end - local widget = wibox.widget(gtable.crush({ - widget = wibox.widget.imagebox, - image = args.image, - forced_height = qvars.char_height, - forced_width = qvars.char_height, - stylesheet = qui.recolor(qvars.colors.fg), - press = args.press, - }, args.widget or {})) - - widget.buttons = { - qbind:new { - triggers = qvars.btns.left, - press = function() - widget:press() - end, - hidden = true, - }, - } - - return widget -end - ---- Generate toggle widget ----@param args table ----@return table -function qui.toggle(args) - args.press = args.press or function(_) end - local widget = qui.button { - widget = gtable.crush({ - toggled = false, - silent_press = function(self, state) - if state then - self.toggled = state - else - self.toggled = not self.toggled - end - - if self.toggled then - self.image = args.on - else - self.image = args.off - end - end, - }, args.widget or {}), - image = args.off, - press = function(self) - if not args.manual then - self:silent_press() - end - args.press(self) - end, - } - - return widget -end - ----@param widget wibox.widget.base ----@param cursor string -function qui.hoverable(widget, cursor) - local hovering = false - - widget:connect_signal("mouse::enter", function() - local w = mouse.current_wibox - if w then - w.cursor = cursor - end - end) - - widget:connect_signal("mouse::leave", function() - local w = mouse.current_wibox - if w then - w.cursor = "left_ptr" - end - end) -end - -return qui diff --git a/.config/awesome/quarrel/ui/consts.lua b/.config/awesome/quarrel/ui/consts.lua new file mode 100644 index 0000000..e6e12d0 --- /dev/null +++ b/.config/awesome/quarrel/ui/consts.lua @@ -0,0 +1,24 @@ +local awful = require "awful" +local qconst = require "quarrel.const" +local wibox = require "wibox" + +local C = {} + +C.BORDER_WIDTH = 1 +C.BORDER_RADIUS = 4 +C.PADDING = 4 +C.BIG_PADDING = 8 +C.TEXT_FONT = "Iosevka Comfy Regular" +C.FONT_SIZE = 9 + +local char_width, char_height = wibox + .widget({ + widget = wibox.widget.textbox, + text = "a", + }) + :get_preferred_size_at_dpi(awful.screen.focused().dpi) + +C.CHAR_HEIGHT = char_height +C.CHAR_WIDTH = char_width + +return C diff --git a/.config/awesome/quarrel/ui/init.lua b/.config/awesome/quarrel/ui/init.lua new file mode 100644 index 0000000..5acaa51 --- /dev/null +++ b/.config/awesome/quarrel/ui/init.lua @@ -0,0 +1,167 @@ +local M = require "quarrel.ui.consts" +local awful = require "awful" +local gshape = require "gears.shape" +local gtable = require "gears.table" +local qbind = require "quarrel.bind" +local qcolor = require "quarrel.color" +local wibox = require "wibox" + +--- Clip Cairo context +---@param cr cairo_surface Cairo surface +---@param w integer Widget width +---@param h integer Widget height +---@return nil +function M.shape(cr, w, h) + -- gears.shape.rounded_rect(cr, w, h, dpi(4)) + gshape.rounded_rect(cr, w, h, M.BORDER_RADIUS) + -- gshape.rectangle(cr, w, h) +end + +--- Returns qvars.text_font with size scaled by factor +---@param factor number? +---@return string +function M.font(factor) + return M.TEXT_FONT .. " " .. M.FONT_SIZE * (factor or 1) +end + +--- Injects background widget styling into target +---@param target table +---@return table +function M.styled(target) + return gtable.crush({ + bg = qcolor.palette.bg(), + border_color = qcolor.palette.border(), + border_width = M.BORDER_WIDTH, + shape = M.shape, + }, target) +end + +--- Generates a styled popup +---@param target table +---@return table +function M.popup(target, debug) + target.widget = { + widget = wibox.container.margin, + margins = M.BIG_PADDING, + target.widget, + } + + return awful.popup(M.styled(target)) +end + +--- Generates svg recolor string +---@param color string +---@return string +function M.recolor(color) + return "svg{fill:" .. color .. "}" +end + +--- Generates icon widget +---@param args table +---@return table +function M.icon(args) + return wibox.widget(gtable.crush({ + widget = wibox.widget.imagebox, + image = args.icon, + forced_width = (not args.unsized) and M.CHAR_HEIGHT, + forced_height = (not args.unsized) and M.CHAR_HEIGHT, + stylesheet = M.recolor(args.color or qcolor.palette.fg()), + }, args.widget or {})) +end + +--- Generates button widget +---@param args table +---@return table +function M.button(args) + args.press = args.press or function(_) end + local widget = wibox.widget(gtable.crush({ + widget = wibox.widget.imagebox, + image = args.icon, + forced_height = M.CHAR_HEIGHT, + forced_width = M.CHAR_HEIGHT, + stylesheet = M.recolor(qcolor.palette.fg()), + press = args.press, + }, args.widget or {})) + + widget.buttons = { + qbind { + triggers = qbind.btns.left, + press = function() + widget:press() + end, + hidden = true, + }, + } + + return widget +end + +--- Generates toggle widget +---@param args table +---@return table +function M.toggle(args) + args.press = args.press or function(_) end + local widget = M.button { + widget = gtable.crush({ + toggled = false, + silent_press = function(self, state) + if state then + self.toggled = state + else + self.toggled = not self.toggled + end + + if self.toggled then + self.image = args.on + else + self.image = args.off + end + end, + }, args.widget or {}), + icon = args.off, + press = function(self) + if not args.manual then + self:silent_press() + end + args.press(self) + end, + } + + return widget +end + +---@param widget wibox.widget.base +---@param cursor string +---@return wibox.widget.base +function M.hoverable(widget, cursor) + local last_wibox + + widget:connect_signal("mouse::enter", function() + local w = mouse.current_wibox + last_wibox = w + if w then + w.cursor = cursor + end + + if type(widget.hover) == "function" then -- on enter bind + widget.hover() + elseif type(widget.hover) == "table" and widget.hover.enter then -- { enter: fun(), leave: fun() } + widget.hover.enter() + end + end) + + widget:connect_signal("mouse::leave", function() + local w = last_wibox or mouse.current_wibox + if w then + w.cursor = "left_ptr" + end + + if type(widget.hover) == "table" and widget.hover.leave then -- { enter: fun(), leave: fun() } + widget.hover.leave() + end + end) + + return widget +end + +return M diff --git a/.config/awesome/quarrel/vars.lua b/.config/awesome/quarrel/vars.lua index 1983343..5c3b178 100644 --- a/.config/awesome/quarrel/vars.lua +++ b/.config/awesome/quarrel/vars.lua @@ -1,37 +1,38 @@ local awful = require "awful" local btns = awful.button.names -local gears = require "gears" -local xresources = require "beautiful.xresources" -local x_col = xresources.get_current_theme() -local dpi = xresources.apply_dpi +local gshape = require "gears.shape" local wibox = require "wibox" +-- local qconts = require "quarrel.consts" +local qbezier = require "quarrel.animation.bezier" ----@class QuarrelVars local qvars = {} -qvars.anim_duration = 0.20 -qvars.anim_intro = qvars.anim_duration / 4 - -qvars.notif_timeout = 3 +-- ---@param protected (fun(): table) | table +-- function qconsts.protect(protected) +-- ---@type table +-- local tbl +-- if type(protected) == "table" then +-- tbl = protected +-- elseif type(protected) == "function" then +-- tbl = protected() +-- else +-- error("expected a table or a function that returns a table, got " .. type(protected), 2) +-- end +-- return setmetatable({}, { +-- __index = tbl, +-- __newindex = function(t, k, v) +-- error("attempted to change constant " .. tostring(k) .. " to " .. tostring(v), 2) +-- end +-- }) +-- end + +-- qui.BORDER_WIDTH = 1 +-- qui.BORDER_RADIUS = 4 + +-- qui.PADDING = 4 +-- qui.BIG_PADDING = 8 ---- Clip Cairo context ----@param cr cairo_surface Cairo surface ----@param w integer Widget width ----@param h integer Widget height ----@return nil -function qvars.shape(cr, w, h) - gears.shape.rounded_rect(cr, w, h, dpi(4)) -end - -qvars.border_width = dpi(1.5) - -qvars.padding = dpi(4) -qvars.big_padding = dpi(8) - --- qvars.text_font = "Fira Code Nerd Font Mono Medium" --- qvars.text_font = "Iosevka Comfy SemiBold" qvars.text_font = "Iosevka Comfy Regular" --- qvars.font_size = 8 qvars.font_size = 9 qvars.font = qvars.text_font .. " " .. qvars.font_size @@ -42,63 +43,152 @@ local char_width, char_height = wibox }) :get_preferred_size_at_dpi(awful.screen.focused().dpi) -qvars.char_height = char_height -qvars.char_width = char_width - -qvars.bar_size = dpi(24) + qvars.big_padding * 2 -qvars.element_size = dpi(12) -qvars.expanded_bar_size = qvars.big_padding + (qvars.big_padding * 2 + qvars.element_size * 4) * 3 + qvars.padding * 2 - -qvars.colors = { - fg = x_col.foreground, - bg = x_col.background, - - black = x_col.color0, - red = x_col.color1, - green = x_col.color2, - yellow = x_col.color3, - blue = x_col.color4, - pink = x_col.color5, - cyan = x_col.color6, - white = x_col.color7, - - bright = { - black = x_col.color8, - red = x_col.color9, - green = x_col.color10, - yellow = x_col.color11, - blue = x_col.color12, - pink = x_col.color13, - cyan = x_col.color14, - white = x_col.color15, - }, - - dim = { - fg = "#77828c", - bg = "#161b22", - }, - - transparent = "#00000000", -} +-- qui.CHAR_HEIGHT = char_height +-- qui.CHAR_WIDTH = char_width + +--- Clip Cairo context +---@param cr cairo_surface Cairo surface +---@param w integer Widget width +---@param h integer Widget height +---@return nil +-- function qui.shape(cr, w, h) +-- -- gears.shape.rounded_rect(cr, w, h, dpi(4)) +-- gshape.rounded_rect(cr, w, h, qui.BORDER_RADIUS) +-- end + +qvars.anim_duration = 0.30 +qvars.anim_intro = qvars.anim_duration / 4 + +local easing_bezier = qbezier(0.2, 0, 0, 1) +function qvars.easing(t, b, c, d) + local epsilon = 1000 / d + return c * easing_bezier:solve(t / d, epsilon) + b +end + +qvars.notif_timeout = 3 + +-- qvars.bar_size = dpi(24) + qui.BIG_PADDING * 2 +-- qvars.element_size = dpi(12) +-- qvars.bar_size = 24 + qui.BIG_PADDING * 2 +-- qvars.element_size = 12 +-- qvars.expanded_bar_size = qui.BIG_PADDING + (qui.BIG_PADDING * 2 + qvars.element_size * 4) * 4 + qui.PADDING * 2 + +-- local function palette(colors) +-- return setmetatable(colors, { +-- __call = function(self) +-- return self[1] +-- end, +-- }) +-- end + +-- local colors = require "quarrel.color" +-- qcolor.palette = colors.palette +-- qcolor.palette = { +-- fg = palette { +-- low = "#a8afb7", +-- "#c8ced7", +-- high = "#e8eff8", +-- }, +-- +-- bg = palette { +-- lowest = "#070c11", +-- low = "#0c1116", +-- "#12161c", +-- high = "#171c22", +-- highest = "#1d2228", +-- }, +-- +-- border = palette { +-- "#323b46", +-- variant = "#262f39", +-- }, +-- +-- red = palette { +-- "#ff928a", +-- bright = "#ffb2a9", +-- }, +-- orange = palette { +-- "#ff9f6f", +-- bright = "#ffc08e", +-- }, +-- yellow = palette { +-- "#ecb256", +-- bright = "#ffd278", +-- }, +-- green = palette { +-- "#8bd294", +-- bright = "#abf3b3", +-- }, +-- cyan = palette { +-- "#6dd3c2", +-- bright = "#8ef4e2", +-- }, +-- blue = palette { +-- "#8bc3fc", +-- bright = "#abe4ff", +-- }, +-- purple = palette { +-- "#c4b1f6", +-- bright = "#e4d1ff", +-- }, +-- pink = palette { +-- "#e5acb4", +-- bright = "#ffccd4", +-- }, +-- +-- transparent = "#00000000", +-- } + +-- qcolor.palette = { +-- fg = x_col.foreground, +-- bg = x_col.background, +-- +-- black = x_col.color0, +-- red = x_col.color1, +-- green = x_col.color2, +-- yellow = x_col.color3, +-- blue = x_col.color4, +-- pink = x_col.color5, +-- cyan = x_col.color6, +-- white = x_col.color7, +-- +-- bright = { +-- black = x_col.color8, +-- red = x_col.color9, +-- green = x_col.color10, +-- yellow = x_col.color11, +-- blue = x_col.color12, +-- pink = x_col.color13, +-- cyan = x_col.color14, +-- white = x_col.color15, +-- }, +-- +-- dim = { +-- fg = "#77828c", +-- bg = "#161b22", +-- }, +-- +-- transparent = "#00000000", +-- } -- taken from https://github.com/bew/dotfiles/blob/ab9bb1935783f7a31ef777b1d7e26d53f35df864/gui/wezterm/cfg_utils.lua -qvars.mods = setmetatable({ _SHORT_MAP = { C = "Control", S = "Shift", A = "Mod1", M = "Mod4" } }, { - __index = function(self, key) - local resolved_mods = {} - for i = 1, #key do - resolved_mods[i] = self._SHORT_MAP[key:sub(i, i)] - end - return resolved_mods - end, -}) - ----@enum buttons -qvars.btns = { - left = btns.LEFT, - right = btns.RIGHT, - middle = btns.MIDDLE, - up = btns.SCROLL_UP, - down = btns.SCROLL_DOWN, -} +-- qbind.mods = setmetatable({ _SHORT_MAP = { C = "Control", S = "Shift", A = "Mod1", M = "Mod4" } }, { +-- __index = function(self, key) +-- local resolved_mods = {} +-- for i = 1, #key do +-- resolved_mods[i] = self._SHORT_MAP[key:sub(i, i)] +-- end +-- return resolved_mods +-- end, +-- }) +-- +-- ---@enum buttons +-- qbind.btns = { +-- left = btns.LEFT, +-- right = btns.RIGHT, +-- middle = btns.MIDDLE, +-- up = btns.SCROLL_UP, +-- down = btns.SCROLL_DOWN, +-- } return qvars |