Differences From
Artifact [4b787982a7]:
1 1 -- [ʞ] sirsem.lua
2 --- ~ lexu hale <lexi@hale.su>
2 +-- ~ lexi hale <lexi@hale.su>
3 +-- glowpelt (hsl conversion)
3 4 -- ? utility library with functionality common to
4 5 -- cortav.lua and its extensions
5 6 -- from Ranuir "software utility"
6 7 -- > local ss = require 'sirsem.lua'
7 8
8 9 local ss
9 10 do -- pull ourselves up by our own bootstraps
................................................................................
43 44 local new = {}
44 45 for k, v in pairs(list) do
45 46 local nk,nv = fn(k,v)
46 47 new[nk or k] = nv or v
47 48 end
48 49 return new
49 50 end
51 +function ss.tmap(fn, a, ...)
52 + if a == nil then return end
53 + return fn(a), ss.tmap(fn, ...)
54 +end
50 55
51 56 function ss.kfilter(list, fn)
52 57 local new = {}
53 58 for k, v in pairs(list) do
54 59 if fn(k,v) then new[k] = v end
55 60 end
56 61 return new
................................................................................
546 551
547 552 function ss.declare(c)
548 553 local cls = setmetatable({
549 554 __name = c.ident;
550 555 }, {
551 556 __name = 'class';
552 557 __tostring = function() return c.ident or '(class)' end;
558 + __index = c.cfns;
553 559 })
554 560
555 561 cls.__call = c.call
556 562 cls.__index = function(self, k)
557 563 if c.default and c.default[k] then
558 564 return c.default[k]
559 565 end
................................................................................
587 593 if c.cast.string then
588 594 cls.__tostring = c.cast.string
589 595 end
590 596 if c.cast.number then
591 597 cls.__tonumber = c.cast.number
592 598 end
593 599 end
600 +
601 + if c.op then
602 + cls.__add = c.op.sum
603 + cls.__sub = c.op.sub
604 + cls.__div = c.op.div
605 + cls.__mul = c.op.mul
606 + cls.__concat = c.op.cat
607 + end
594 608
595 609 cls.mk = function(...)
596 610 local val = setmetatable(c.mk and c.mk(...) or {}, cls)
597 611 if c.init then
598 612 for k,v in pairs(c.init) do
599 613 val[k] = v
600 614 end
................................................................................
885 899 end
886 900 else
887 901 me:react(sym)
888 902 end
889 903 end;
890 904 };
891 905 }
906 +
907 +function ss.math.clamp(v, l, h)
908 + return math.max(math.min(v, h or 1), l or 0)
909 +end
892 910
893 911 -- convenience buffer for holding strings under
894 912 -- construction, accumulating and compiling then in
895 913 -- as quick a way as lua permits
896 914 ss.strac = ss.declare {
897 915 ident = 'string-accumulator';
898 916 mk = function() return {
................................................................................
935 953 end;
936 954 wrap = function(self,a,b)
937 955 table.insert(self.strs, 1, a)
938 956 table.insert(self.strs, b)
939 957 end;
940 958 };
941 959 }
960 +
961 +-- color class based on c.hale.su/sorcery's, hsl conversion
962 +-- code written by glowpelt. TODO switch to LCH
963 +local function clip(v, ...)
964 + if v == nil then return end
965 + return math.max(0,math.min(0xFF,math.floor(v))), clip(...)
966 +end;
967 +local function bytefrac(f, ...)
968 + if f == nil then return end
969 + return clip(f*0xFF), bytefrac(...)
970 +end
971 +ss.color = ss.declare {
972 + ident = 'color';
973 + mk = function(h,s,l,a) return {
974 + hue = h or 0.0;
975 + sat = s or 0.0;
976 + lum = l or 0.0;
977 + alpha = a or 1.0;
978 + } end;
979 + cfns = {
980 + byteclip = clip;
981 + bytefrac = bytefrac;
982 + };
983 + cast = {
984 + string = function(self) return self:hex() end;
985 + number = function(self) return self:u32() end;
986 + };
987 + op = {
988 + sum = function(self, other)
989 + if ss.color.is(other) then
990 + local fac = ss.math.lerp(self.alpha, 1, other.alpha)
991 + return self:blend(other, fac):warp(function(c)
992 + c.alpha = ss.math.clamp(self.alpha+other.alpha)
993 + end)
994 + else -- color + number = brighter color
995 + return self:warp(function(c)
996 + c.lum = c.lum + other
997 + end)
998 + end
999 + end;
1000 + mul = function(self, other)
1001 + if ss.color.is(other) then
1002 + ss.color.exn 'how the heck do you multiply in hsl anyway':throw()
1003 + else
1004 + return self:warp(function(c)
1005 + c.lum = c.lum * other
1006 + end)
1007 + end
1008 + end;
1009 + };
1010 + fns = {
1011 + tuple = function(self)
1012 + return self.hue, self.sat, self.lum, self.alpha
1013 + end;
1014 + warp = function(self, func)
1015 + local n = self:clone()
1016 + func(n)
1017 + return n
1018 + end;
1019 + blend = function(self, other, fac)
1020 + return ss.color(
1021 + ss.math.lerp(fac, self.hue, other.hue),
1022 + ss.math.lerp(fac, self.sat, other.sat),
1023 + ss.math.lerp(fac, self.lum, other.lum),
1024 + ss.math.lerp(fac, self.alpha, other.alpha))
1025 + end;
1026 + hex = function(self)
1027 + local r,g,b,a = bytefrac(self:rgb_t())
1028 + if self.alpha == 1 then a = nil end
1029 + return string.format('#'..string.rep('%02x',a and 4 or 3),
1030 + r,g,b,a)
1031 + end;
1032 + u32 = function(self)
1033 + local r,g,b,a = bytefrac(self:rgb_t())
1034 + return r<<24 | g << 16 | b << 8 | a
1035 + end;
1036 + bytes = function(self)
1037 + return { bytefrac(self:rgb_t()) }
1038 + end;
1039 + alt = function(self, fld, new)
1040 + if self[fld] then
1041 + return self:warp(function(c) c[fld]=new end)
1042 + else
1043 + ss.color.exn('no such field %s in color', fld):throw()
1044 + end
1045 + end;
1046 + rgb = function(self)
1047 + -- convenience function to get a standardized struct
1048 + local r,g,b,a = self:rgb_t()
1049 + return {
1050 + red = r;
1051 + green = g;
1052 + blue = b;
1053 + alpha = a;
1054 + }
1055 + end;
1056 + rgb_t = function(self)
1057 + -- returns rgba values as a tuple
1058 + local value = function(n1, n2, hue)
1059 + if hue > 360 then
1060 + hue = hue - 360
1061 + elseif hue < 0 then
1062 + hue = hue + 360
1063 + end
1064 + if hue < 60 then
1065 + return n1 + (n2 - n1) * hue/60
1066 + elseif hue < 180 then
1067 + return n2
1068 + elseif hue < 240 then
1069 + return n1 + (n2 - n1) * (240 - hue)/60
1070 + else
1071 + return n1
1072 + end
1073 + end
1074 + local h,s,l,alpha = self:tuple()
1075 + local m2
1076 + if l < 0.5 then
1077 + m2 = l * (1 + s)
1078 + else
1079 + m2 = l + s - l * s
1080 + end
1081 + local m1 = 2 * l - m2
1082 + if s == 0 then
1083 + -- Achromatic, there is no hue
1084 + -- In book this errors if hue is not undefined, but we set hue to 0 in this case, not nil or something, so
1085 + return l, l, l, alpha
1086 + else
1087 + -- Chromatic case, so there is a hue
1088 + return
1089 + value(m1, m2, h + 120),
1090 + value(m1, m2, h),
1091 + value(m1, m2, h - 120),
1092 + alpha
1093 + end
1094 + end;
1095 + };
1096 +};
1097 +ss.color.exn = ss.exnkind 'color error'
1098 +
1099 +ss.cmdfmt = function(cmd, ...)
1100 + return string.format(cmd, ss.tmap(function(s)
1101 + if typeof(s) == 'string' then
1102 + return string.format("%q", s)
1103 + -- FIXME this is incredibly lazy and uses lua quoting, not
1104 + -- bourne shell quoting. it *will* cause problems if anything
1105 + -- exotic finds its way in and needs to be fixed.
1106 + -- TODO provide a proper popen in the C wrapper so wrapped
1107 + -- versions at least can launch programs in a sane and secure
1108 + -- way.
1109 + else
1110 + return s
1111 + end
1112 + end, ...))
1113 +end