cortav  Diff

Differences From Artifact [4b787982a7]:

To Artifact [550cdedbd6]:


     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