Button.lua 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. local Element = require('elements/Element')
  2. ---@alias ButtonProps {icon: string; on_click: function; anchor_id?: string; active?: boolean; badge?: string|number; foreground?: string; background?: string; tooltip?: string}
  3. ---@class Button : Element
  4. local Button = class(Element)
  5. ---@param id string
  6. ---@param props ButtonProps
  7. function Button:new(id, props) return Class.new(self, id, props) --[[@as Button]] end
  8. ---@param id string
  9. ---@param props ButtonProps
  10. function Button:init(id, props)
  11. self.icon = props.icon
  12. self.active = props.active
  13. self.tooltip = props.tooltip
  14. self.badge = props.badge
  15. self.foreground = props.foreground or fg
  16. self.background = props.background or bg
  17. ---@type fun()
  18. self.on_click = props.on_click
  19. Element.init(self, id, props)
  20. end
  21. function Button:on_coordinates() self.font_size = round((self.by - self.ay) * 0.7) end
  22. function Button:handle_cursor_click()
  23. -- We delay the callback to next tick, otherwise we are risking race
  24. -- conditions as we are in the middle of event dispatching.
  25. -- For example, handler might add a menu to the end of the element stack, and that
  26. -- than picks up this click event we are in right now, and instantly closes itself.
  27. mp.add_timeout(0.01, self.on_click)
  28. end
  29. function Button:render()
  30. local visibility = self:get_visibility()
  31. if visibility <= 0 then return end
  32. cursor:zone('primary_click', self, function() self:handle_cursor_click() end)
  33. local ass = assdraw.ass_new()
  34. local is_hover = self.proximity_raw == 0
  35. local is_hover_or_active = is_hover or self.active
  36. local foreground = self.active and self.background or self.foreground
  37. local background = self.active and self.foreground or self.background
  38. -- Background
  39. if is_hover_or_active or config.opacity.controls > 0 then
  40. ass:rect(self.ax, self.ay, self.bx, self.by, {
  41. color = (self.active or not is_hover) and background or foreground,
  42. radius = state.radius,
  43. opacity = visibility * (self.active and 1 or (is_hover and 0.3 or config.opacity.controls)),
  44. })
  45. end
  46. -- Tooltip on hover
  47. if is_hover and self.tooltip then ass:tooltip(self, self.tooltip) end
  48. -- Badge
  49. local icon_clip
  50. if self.badge then
  51. local badge_font_size = self.font_size * 0.6
  52. local badge_opts = {size = badge_font_size, color = background, opacity = visibility}
  53. local badge_width = text_width(self.badge, badge_opts)
  54. local width, height = math.ceil(badge_width + (badge_font_size / 7) * 2), math.ceil(badge_font_size * 0.93)
  55. local bx, by = self.bx - 1, self.by - 1
  56. ass:rect(bx - width, by - height, bx, by, {
  57. color = foreground,
  58. radius = state.radius,
  59. opacity = visibility,
  60. border = self.active and 0 or 1,
  61. border_color = background,
  62. })
  63. ass:txt(bx - width / 2, by - height / 2, 5, self.badge, badge_opts)
  64. local clip_border = math.max(self.font_size / 20, 1)
  65. local clip_path = assdraw.ass_new()
  66. clip_path:round_rect_cw(
  67. math.floor((bx - width) - clip_border), math.floor((by - height) - clip_border), bx, by, 3
  68. )
  69. icon_clip = '\\iclip(' .. clip_path.scale .. ', ' .. clip_path.text .. ')'
  70. end
  71. -- Icon
  72. local x, y = round(self.ax + (self.bx - self.ax) / 2), round(self.ay + (self.by - self.ay) / 2)
  73. ass:icon(x, y, self.font_size, self.icon, {
  74. color = foreground,
  75. border = self.active and 0 or options.text_border * state.scale,
  76. border_color = background,
  77. opacity = visibility,
  78. clip = icon_clip,
  79. })
  80. return ass
  81. end
  82. return Button