ass.lua 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. --[[ ASSDRAW EXTENSIONS ]]
  2. local ass_mt = getmetatable(assdraw.ass_new())
  3. -- Opacity.
  4. ---@param self table|nil
  5. ---@param opacity number|{primary?: number; border?: number, shadow?: number, main?: number} Opacity of all elements.
  6. ---@param fraction? number Optionally adjust the above opacity by this fraction.
  7. ---@return string|nil
  8. function ass_mt.opacity(self, opacity, fraction)
  9. fraction = fraction ~= nil and fraction or 1
  10. opacity = type(opacity) == 'table' and opacity or {main = opacity}
  11. local text = ''
  12. if opacity.main then
  13. text = text .. string.format('\\alpha&H%X&', opacity_to_alpha(opacity.main * fraction))
  14. end
  15. if opacity.primary then
  16. text = text .. string.format('\\1a&H%X&', opacity_to_alpha(opacity.primary * fraction))
  17. end
  18. if opacity.border then
  19. text = text .. string.format('\\3a&H%X&', opacity_to_alpha(opacity.border * fraction))
  20. end
  21. if opacity.shadow then
  22. text = text .. string.format('\\4a&H%X&', opacity_to_alpha(opacity.shadow * fraction))
  23. end
  24. if self == nil then
  25. return text
  26. elseif text ~= '' then
  27. self.text = self.text .. '{' .. text .. '}'
  28. end
  29. end
  30. -- Icon.
  31. ---@param x number
  32. ---@param y number
  33. ---@param size number
  34. ---@param name string
  35. ---@param opts? {color?: string; border?: number; border_color?: string; opacity?: number; clip?: string; align?: number}
  36. function ass_mt:icon(x, y, size, name, opts)
  37. opts = opts or {}
  38. opts.font, opts.size, opts.bold = 'MaterialIconsRound-Regular', size, false
  39. self:txt(x, y, opts.align or 5, name, opts)
  40. end
  41. -- Text.
  42. -- Named `txt` because `ass.text` is a value.
  43. ---@param x number
  44. ---@param y number
  45. ---@param align number
  46. ---@param value string|number
  47. ---@param opts {size: number; font?: string; color?: string; bold?: boolean; italic?: boolean; border?: number; border_color?: string; shadow?: number; shadow_color?: string; rotate?: number; wrap?: number; opacity?: number|{primary?: number; border?: number, shadow?: number, main?: number}; clip?: string}
  48. function ass_mt:txt(x, y, align, value, opts)
  49. local border_size = opts.border or 0
  50. local shadow_size = opts.shadow or 0
  51. local tags = '\\pos(' .. x .. ',' .. y .. ')\\rDefault\\an' .. align .. '\\blur0'
  52. -- font
  53. tags = tags .. '\\fn' .. (opts.font or config.font)
  54. -- font size
  55. tags = tags .. '\\fs' .. opts.size
  56. -- bold
  57. if opts.bold or (opts.bold == nil and options.font_bold) then tags = tags .. '\\b1' end
  58. -- italic
  59. if opts.italic then tags = tags .. '\\i1' end
  60. -- rotate
  61. if opts.rotate then tags = tags .. '\\frz' .. opts.rotate end
  62. -- wrap
  63. if opts.wrap then tags = tags .. '\\q' .. opts.wrap end
  64. -- border
  65. tags = tags .. '\\bord' .. border_size
  66. -- shadow
  67. tags = tags .. '\\shad' .. shadow_size
  68. -- colors
  69. tags = tags .. '\\1c&H' .. (opts.color or bgt)
  70. if border_size > 0 then tags = tags .. '\\3c&H' .. (opts.border_color or bg) end
  71. if shadow_size > 0 then tags = tags .. '\\4c&H' .. (opts.shadow_color or bg) end
  72. -- opacity
  73. if opts.opacity then tags = tags .. self.opacity(nil, opts.opacity) end
  74. -- clip
  75. if opts.clip then tags = tags .. opts.clip end
  76. -- render
  77. self:new_event()
  78. self.text = self.text .. '{' .. tags .. '}' .. value
  79. end
  80. -- Tooltip.
  81. ---@param element Rect
  82. ---@param value string|number
  83. ---@param opts? {size?: number; offset?: number; bold?: boolean; italic?: boolean; width_overwrite?: number, margin?: number; responsive?: boolean; lines?: integer, timestamp?: boolean}
  84. function ass_mt:tooltip(element, value, opts)
  85. if value == '' then return end
  86. opts = opts or {}
  87. opts.size = opts.size or round(16 * state.scale)
  88. opts.border = options.text_border * state.scale
  89. opts.border_color = bg
  90. opts.margin = opts.margin or round(10 * state.scale)
  91. opts.lines = opts.lines or 1
  92. local padding_y = round(opts.size / 6)
  93. local padding_x = round(opts.size / 3)
  94. local offset = opts.offset or 2
  95. local align_top = opts.responsive == false or element.ay - offset > opts.size * 2
  96. local x = element.ax + (element.bx - element.ax) / 2
  97. local y = align_top and element.ay - offset or element.by + offset
  98. local width_half = (opts.width_overwrite or text_width(value, opts)) / 2 + padding_x
  99. local min_edge_distance = width_half + opts.margin + Elements:v('window_border', 'size', 0)
  100. x = clamp(min_edge_distance, x, display.width - min_edge_distance)
  101. local ax, bx = round(x - width_half), round(x + width_half)
  102. local ay = (align_top and y - opts.size * opts.lines - 2 * padding_y or y)
  103. local by = (align_top and y or y + opts.size * opts.lines + 2 * padding_y)
  104. self:rect(ax, ay, bx, by, {color = bg, opacity = config.opacity.tooltip, radius = state.radius})
  105. local func = opts.timestamp and self.timestamp or self.txt
  106. func(self, x, align_top and y - padding_y or y + padding_y, align_top and 2 or 8, tostring(value), opts)
  107. return {ax = element.ax, ay = ay, bx = element.bx, by = by}
  108. end
  109. -- Timestamp with each digit positioned as if it was replaced with 0
  110. ---@param x number
  111. ---@param y number
  112. ---@param align number
  113. ---@param timestamp string
  114. ---@param opts {size: number; opacity?: number|{primary?: number; border?: number, shadow?: number, main?: number}}
  115. function ass_mt:timestamp(x, y, align, timestamp, opts)
  116. local widths, width_total = {}, 0
  117. zero_rep = timestamp_zero_rep(timestamp)
  118. for i = 1, #zero_rep do
  119. local width = text_width(zero_rep:sub(i, i), opts)
  120. widths[i] = width
  121. width_total = width_total + width
  122. end
  123. -- shift x and y to fit align 5
  124. local mod_align = align % 3
  125. if mod_align == 0 then
  126. x = x - width_total
  127. elseif mod_align == 2 then
  128. x = x - width_total / 2
  129. end
  130. if align < 4 then
  131. y = y - opts.size / 2
  132. elseif align > 6 then
  133. y = y + opts.size / 2
  134. end
  135. local opacity = opts.opacity
  136. local primary_opacity
  137. if type(opacity) == 'table' then
  138. opts.opacity = {main = opacity.main, border = opacity.border, shadow = opacity.shadow, primary = 0}
  139. primary_opacity = opacity.primary or opacity.main
  140. else
  141. opts.opacity = {main = opacity, primary = 0}
  142. primary_opacity = opacity
  143. end
  144. for i, width in ipairs(widths) do
  145. self:txt(x + width / 2, y, 5, timestamp:sub(i, i), opts)
  146. x = x + width
  147. end
  148. x = x - width_total
  149. opts.opacity = {main = 0, primary = primary_opacity or 1}
  150. for i, width in ipairs(widths) do
  151. self:txt(x + width / 2, y, 5, timestamp:sub(i, i), opts)
  152. x = x + width
  153. end
  154. opts.opacity = opacity
  155. end
  156. -- Rectangle.
  157. ---@param ax number
  158. ---@param ay number
  159. ---@param bx number
  160. ---@param by number
  161. ---@param opts? {color?: string; border?: number; border_color?: string; opacity?: number|{primary?: number; border?: number, shadow?: number, main?: number}; clip?: string, radius?: number}
  162. function ass_mt:rect(ax, ay, bx, by, opts)
  163. opts = opts or {}
  164. local border_size = opts.border or 0
  165. local tags = '\\pos(0,0)\\rDefault\\an7\\blur0'
  166. -- border
  167. tags = tags .. '\\bord' .. border_size
  168. -- colors
  169. tags = tags .. '\\1c&H' .. (opts.color or fg)
  170. if border_size > 0 then tags = tags .. '\\3c&H' .. (opts.border_color or bg) end
  171. -- opacity
  172. if opts.opacity then tags = tags .. self.opacity(nil, opts.opacity) end
  173. -- clip
  174. if opts.clip then
  175. tags = tags .. opts.clip
  176. end
  177. -- draw
  178. self:new_event()
  179. self.text = self.text .. '{' .. tags .. '}'
  180. self:draw_start()
  181. if opts.radius and opts.radius > 0 then
  182. self:round_rect_cw(ax, ay, bx, by, opts.radius)
  183. else
  184. self:rect_cw(ax, ay, bx, by)
  185. end
  186. self:draw_stop()
  187. end
  188. -- Circle.
  189. ---@param x number
  190. ---@param y number
  191. ---@param radius number
  192. ---@param opts? {color?: string; border?: number; border_color?: string; opacity?: number; clip?: string}
  193. function ass_mt:circle(x, y, radius, opts)
  194. opts = opts or {}
  195. opts.radius = radius
  196. self:rect(x - radius, y - radius, x + radius, y + radius, opts)
  197. end
  198. -- Texture.
  199. ---@param ax number
  200. ---@param ay number
  201. ---@param bx number
  202. ---@param by number
  203. ---@param char string Texture font character.
  204. ---@param opts {size?: number; color: string; opacity?: number; clip?: string; anchor_x?: number, anchor_y?: number}
  205. function ass_mt:texture(ax, ay, bx, by, char, opts)
  206. opts = opts or {}
  207. local anchor_x, anchor_y = opts.anchor_x or ax, opts.anchor_y or ay
  208. local clip = opts.clip or ('\\clip(' .. ax .. ',' .. ay .. ',' .. bx .. ',' .. by .. ')')
  209. local tile_size, opacity = opts.size or 100, opts.opacity or 0.2
  210. local x, y = ax - (ax - anchor_x) % tile_size, ay - (ay - anchor_y) % tile_size
  211. local width, height = bx - x, by - y
  212. local line = string.rep(char, math.ceil((width / tile_size)))
  213. local lines = ''
  214. for i = 1, math.ceil(height / tile_size), 1 do lines = lines .. (lines == '' and '' or '\\N') .. line end
  215. self:txt(
  216. x, y, 7, lines,
  217. {font = 'uosc_textures', size = tile_size, color = opts.color, bold = false, opacity = opacity, clip = clip})
  218. end
  219. -- Rotating spinner icon.
  220. ---@param x number
  221. ---@param y number
  222. ---@param size number
  223. ---@param opts? {color?: string; opacity?: number; clip?: string; border?: number; border_color?: string;}
  224. function ass_mt:spinner(x, y, size, opts)
  225. opts = opts or {}
  226. opts.rotate = (state.render_last_time * 1.75 % 1) * -360
  227. opts.color = opts.color or fg
  228. self:icon(x, y, size, 'autorenew', opts)
  229. request_render()
  230. end