Advertisement
Guest User

mpv - colored osc

a guest
Feb 6th, 2016
76
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 63.53 KB | None | 0 0
  1. local assdraw = require 'mp.assdraw'
  2. local msg = require 'mp.msg'
  3. local opt = require 'mp.options'
  4.  
  5. --
  6. -- Parameters
  7. --
  8.  
  9. -- default user option values
  10. -- do not touch, change them in osc.conf
  11. local user_opts = {
  12.     showwindowed = true,        -- show OSC when windowed?
  13.     showfullscreen = true,      -- show OSC when fullscreen?
  14.     scalewindowed = 1,          -- scaling of the controller when windowed
  15.     scalefullscreen = 1,        -- scaling of the controller when fullscreen
  16.     scaleforcedwindow = 2,      -- scaling when rendered on a forced window
  17.     vidscale = true,            -- scale the controller with the video?
  18.     valign = 0.8,               -- vertical alignment, -1 (top) to 1 (bottom)
  19.     halign = 0,                 -- horizontal alignment, -1 (left) to 1 (right)
  20.     boxalpha = 80,              -- alpha of the background box,
  21.                                 -- 0 (opaque) to 255 (fully transparent)
  22.     hidetimeout = 500,          -- duration in ms until the OSC hides if no
  23.                                 -- mouse movement, negative value = disabled
  24.     fadeduration = 200,         -- duration of fade out in ms, 0 = no fade
  25.     deadzonesize = 0,           -- size of deadzone
  26.     minmousemove = 3,           -- minimum amount of pixels the mouse has to
  27.                                 -- move between ticks to make the OSC show up
  28.     iamaprogrammer = false,     -- use native mpv values and disable OSC
  29.                                 -- internal track list management (and some
  30.                                 -- functions that depend on it)
  31.     layout = "box",
  32.     seekbarstyle = "slider",    -- slider (diamond marker) or bar (fill)
  33.     timetotal = false,          -- display total time instead of remaining time?
  34.     timems = false,             -- display timecodes with milliseconds?
  35. }
  36.  
  37. -- read options from config and command-line
  38. opt.read_options(user_opts, "osc")
  39.  
  40. local osc_param = { -- calculated by osc_init()
  41.     playresy = 0,                           -- canvas size Y
  42.     playresx = 0,                           -- canvas size X
  43.     display_aspect = 1,
  44.     areas = {},
  45. }
  46.  
  47. local osc_styles = {
  48.     bigButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs50\\fnmpv-osd-symbols}",
  49.     smallButtonsL = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs20\\fnmpv-osd-symbols}",
  50.     smallButtonsLlabel = "{\\fs17\\fn" .. mp.get_property("options/osd-font") .. "}",
  51.     smallButtonsR = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs30\\fnmpv-osd-symbols}",
  52.     topButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\fnmpv-osd-symbols}",
  53.  
  54.     elementDown = "{\\1c&H999999}",
  55.     timecodes = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs20}",
  56.     vidtitle = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12}",
  57.     box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}",
  58. }
  59.  
  60. mp.observe_property("stream-open-filename", "string", function(name, value)
  61. if (value:find("twitch.tv")) or
  62.    (value:find("ttvnw.net")) then
  63.     osc_styles.box = "{\\rDefault\\blur0\\bord1\\1c&HA54164\\3c&HFFFFFF}"
  64. elseif (value:find("youtube.com")) or
  65.        (value:find("googlevideo.com")) then
  66.     osc_styles.box = "{\\rDefault\\blur0\\bord1\\1c&H2324D4\\3c&HFFFFFF}"
  67. elseif (value:find("vimeo.com")) or
  68.        (value:find("vimeocdn.com")) then
  69.     osc_styles.box = "{\\rDefault\\blur0\\bord1\\1c&HEBB920\\3c&HFFFFFF}"
  70. end
  71. end)
  72.  
  73. -- internal states, do not touch
  74. local state = {
  75.     showtime,                               -- time of last invocation (last mouse move)
  76.     osc_visible = false,
  77.     anistart,                               -- time when the animation started
  78.     anitype,                                -- current type of animation
  79.     animation,                              -- current animation alpha
  80.     mouse_down_counter = 0,                 -- used for softrepeat
  81.     active_element = nil,                   -- nil = none, 0 = background, 1+ = see elements[]
  82.     active_event_source = nil,              -- the "button" that issued the current event
  83.     rightTC_trem = not user_opts.timetotal, -- if the right timecode should display total or remaining time
  84.     tc_ms = user_opts.timems,               -- Should the timecodes display their time with milliseconds
  85.     mp_screen_sizeX, mp_screen_sizeY,       -- last screen-resolution, to detect resolution changes to issue reINITs
  86.     initREQ = false,                        -- is a re-init request pending?
  87.     last_mouseX, last_mouseY,               -- last mouse position, to detect siginificant mouse movement
  88.     message_text,
  89.     message_timeout,
  90.     fullscreen = false,
  91.     timer = nil,
  92.     cache_idle = false,
  93.     idle = false,
  94.     enabled = true,
  95.     input_enabled = true,
  96.     showhide_enabled = false,
  97. }
  98.  
  99.  
  100.  
  101.  
  102. --
  103. -- Helperfunctions
  104. --
  105.  
  106. function scale_value(x0, x1, y0, y1, val)
  107.     local m = (y1 - y0) / (x1 - x0)
  108.     local b = y0 - (m * x0)
  109.     return (m * val) + b
  110. end
  111.  
  112. -- returns hitbox spanning coordinates (top left, bottom right corner)
  113. -- according to alignment
  114. function get_hitbox_coords(x, y, an, w, h)
  115.  
  116.     local alignments = {
  117.       [1] = function () return x, y-h, x+w, y end,
  118.       [2] = function () return x-(w/2), y-h, x+(w/2), y end,
  119.       [3] = function () return x-w, y-h, x, y end,
  120.  
  121.       [4] = function () return x, y-(h/2), x+w, y+(h/2) end,
  122.       [5] = function () return x-(w/2), y-(h/2), x+(w/2), y+(h/2) end,
  123.       [6] = function () return x-w, y-(h/2), x, y+(h/2) end,
  124.  
  125.       [7] = function () return x, y, x+w, y+h end,
  126.       [8] = function () return x-(w/2), y, x+(w/2), y+h end,
  127.       [9] = function () return x-w, y, x, y+h end,
  128.     }
  129.  
  130.     return alignments[an]()
  131. end
  132.  
  133. function get_hitbox_coords_geo(geometry)
  134.     return get_hitbox_coords(geometry.x, geometry.y, geometry.an,
  135.         geometry.w, geometry.h)
  136. end
  137.  
  138. function get_element_hitbox(element)
  139.     return element.hitbox.x1, element.hitbox.y1,
  140.         element.hitbox.x2, element.hitbox.y2
  141. end
  142.  
  143. function mouse_hit(element)
  144.     return mouse_hit_coords(get_element_hitbox(element))
  145. end
  146.  
  147. function mouse_hit_coords(bX1, bY1, bX2, bY2)
  148.     local mX, mY = mp.get_mouse_pos()
  149.     return (mX >= bX1 and mX <= bX2 and mY >= bY1 and mY <= bY2)
  150. end
  151.  
  152. function limit_range(min, max, val)
  153.     if val > max then
  154.         val = max
  155.     elseif val < min then
  156.         val = min
  157.     end
  158.     return val
  159. end
  160.  
  161. -- translate value into element coordinates
  162. function get_slider_ele_pos_for(element, val)
  163.  
  164.     local ele_pos = scale_value(
  165.         element.slider.min.value, element.slider.max.value,
  166.         element.slider.min.ele_pos, element.slider.max.ele_pos,
  167.         val)
  168.  
  169.     return limit_range(
  170.         element.slider.min.ele_pos, element.slider.max.ele_pos,
  171.         ele_pos)
  172. end
  173.  
  174. -- translates global (mouse) coordinates to value
  175. function get_slider_value_at(element, glob_pos)
  176.  
  177.     local val = scale_value(
  178.         element.slider.min.glob_pos, element.slider.max.glob_pos,
  179.         element.slider.min.value, element.slider.max.value,
  180.         glob_pos)
  181.  
  182.     return limit_range(
  183.         element.slider.min.value, element.slider.max.value,
  184.         val)
  185. end
  186.  
  187. -- get value at current mouse position
  188. function get_slider_value(element)
  189.     return get_slider_value_at(element, mp.get_mouse_pos())
  190. end
  191.  
  192. function countone(val)
  193.     if not (user_opts.iamaprogrammer) then
  194.         val = val + 1
  195.     end
  196.     return val
  197. end
  198.  
  199. -- align:  -1 .. +1
  200. -- frame:  size of the containing area
  201. -- obj:    size of the object that should be positioned inside the area
  202. -- margin: min. distance from object to frame (as long as -1 <= align <= +1)
  203. function get_align(align, frame, obj, margin)
  204.     return (frame / 2) + (((frame / 2) - margin - (obj / 2)) * align)
  205. end
  206.  
  207. -- multiplies two alpha values, formular can probably be improved
  208. function mult_alpha(alphaA, alphaB)
  209.     return 255 - (((1-(alphaA/255)) * (1-(alphaB/255))) * 255)
  210. end
  211.  
  212. function add_area(name, x1, y1, x2, y2)
  213.     -- create area if needed
  214.     if (osc_param.areas[name] == nil) then
  215.         osc_param.areas[name] = {}
  216.     end
  217.     table.insert(osc_param.areas[name], {x1=x1, y1=y1, x2=x2, y2=y2})
  218. end
  219.  
  220.  
  221. --
  222. -- Tracklist Management
  223. --
  224.  
  225. local nicetypes = {video = "Video", audio = "Audio", sub = "Subtitle"}
  226.  
  227. -- updates the OSC internal playlists, should be run each time the track-layout changes
  228. function update_tracklist()
  229.     local tracktable = mp.get_property_native("track-list", {})
  230.  
  231.     -- by osc_id
  232.     tracks_osc = {}
  233.     tracks_osc.video, tracks_osc.audio, tracks_osc.sub = {}, {}, {}
  234.     -- by mpv_id
  235.     tracks_mpv = {}
  236.     tracks_mpv.video, tracks_mpv.audio, tracks_mpv.sub = {}, {}, {}
  237.     for n = 1, #tracktable do
  238.         if not (tracktable[n].type == "unknown") then
  239.             local type = tracktable[n].type
  240.             local mpv_id = tonumber(tracktable[n].id)
  241.  
  242.             -- by osc_id
  243.             table.insert(tracks_osc[type], tracktable[n])
  244.  
  245.             -- by mpv_id
  246.             tracks_mpv[type][mpv_id] = tracktable[n]
  247.             tracks_mpv[type][mpv_id].osc_id = #tracks_osc[type]
  248.         end
  249.     end
  250. end
  251.  
  252. -- return a nice list of tracks of the given type (video, audio, sub)
  253. function get_tracklist(type)
  254.     local msg = "Available " .. nicetypes[type] .. " Tracks: "
  255.     if #tracks_osc[type] == 0 then
  256.         msg = msg .. "none"
  257.     else
  258.         for n = 1, #tracks_osc[type] do
  259.             local track = tracks_osc[type][n]
  260.             local lang, title, selected = "unknown", "", "○"
  261.             if not(track.lang == nil) then lang = track.lang end
  262.             if not(track.title == nil) then title = track.title end
  263.             if (track.id == tonumber(mp.get_property(type))) then
  264.                 selected = "●"
  265.             end
  266.             msg = msg.."\n"..selected.." "..n..": ["..lang.."] "..title
  267.         end
  268.     end
  269.     return msg
  270. end
  271.  
  272. -- relatively change the track of given <type> by <next> tracks
  273.     --(+1 -> next, -1 -> previous)
  274. function set_track(type, next)
  275.     local current_track_mpv, current_track_osc
  276.     if (mp.get_property(type) == "no") then
  277.         current_track_osc = 0
  278.     else
  279.         current_track_mpv = tonumber(mp.get_property(type))
  280.         current_track_osc = tracks_mpv[type][current_track_mpv].osc_id
  281.     end
  282.     local new_track_osc = (current_track_osc + next) % (#tracks_osc[type] + 1)
  283.     local new_track_mpv
  284.     if new_track_osc == 0 then
  285.         new_track_mpv = "no"
  286.     else
  287.         new_track_mpv = tracks_osc[type][new_track_osc].id
  288.     end
  289.  
  290.     mp.commandv("set", type, new_track_mpv)
  291.  
  292.         if (new_track_osc == 0) then
  293.         show_message(nicetypes[type] .. " Track: none")
  294.     else
  295.         show_message(nicetypes[type]  .. " Track: "
  296.             .. new_track_osc .. "/" .. #tracks_osc[type]
  297.             .. " [".. (tracks_osc[type][new_track_osc].lang or "unknown") .."] "
  298.             .. (tracks_osc[type][new_track_osc].title or ""))
  299.     end
  300. end
  301.  
  302. -- get the currently selected track of <type>, OSC-style counted
  303. function get_track(type)
  304.     local track = mp.get_property(type)
  305.     if track ~= "no" and track ~= nil then
  306.         local tr = tracks_mpv[type][tonumber(track)]
  307.         if tr then
  308.             return tr.osc_id
  309.         end
  310.     end
  311.     return 0
  312. end
  313.  
  314.  
  315. --
  316. -- Element Management
  317. --
  318.  
  319. local elements = {}
  320.  
  321. function prepare_elements()
  322.  
  323.     -- remove elements without layout or invisble
  324.     local elements2 = {}
  325.     for n, element in pairs(elements) do
  326.         if not (element.layout == nil) and (element.visible) then
  327.             table.insert(elements2, element)
  328.         end
  329.     end
  330.     elements = elements2
  331.  
  332.     function elem_compare (a, b)
  333.         return a.layout.layer < b.layout.layer
  334.     end
  335.  
  336.     table.sort(elements, elem_compare)
  337.  
  338.  
  339.     for _,element in pairs(elements) do
  340.  
  341.         local elem_geo = element.layout.geometry
  342.  
  343.         -- Calculate the hitbox
  344.         local bX1, bY1, bX2, bY2 = get_hitbox_coords_geo(elem_geo)
  345.         element.hitbox = {x1 = bX1, y1 = bY1, x2 = bX2, y2 = bY2}
  346.  
  347.         local style_ass = assdraw.ass_new()
  348.  
  349.         -- prepare static elements
  350.         style_ass:append("{}") -- hack to troll new_event into inserting a \n
  351.         style_ass:new_event()
  352.         style_ass:pos(elem_geo.x, elem_geo.y)
  353.         style_ass:an(elem_geo.an)
  354.         style_ass:append(element.layout.style)
  355.  
  356.         element.style_ass = style_ass
  357.  
  358.         local static_ass = assdraw.ass_new()
  359.  
  360.  
  361.         if (element.type == "box") then
  362.             --draw box
  363.             static_ass:draw_start()
  364.             static_ass:round_rect_cw(0, 0, elem_geo.w, elem_geo.h,
  365.                 element.layout.box.radius)
  366.             static_ass:draw_stop()
  367.  
  368.  
  369.         elseif (element.type == "slider") then
  370.             --draw static slider parts
  371.  
  372.             local slider_lo = element.layout.slider
  373.             -- offset between element outline and drag-area
  374.             local foV = slider_lo.border + slider_lo.gap
  375.  
  376.             -- calculate positions of min and max points
  377.             if (slider_lo.stype == "slider") then
  378.                 element.slider.min.ele_pos = elem_geo.h / 2
  379.                 element.slider.max.ele_pos = elem_geo.w - (elem_geo.h / 2)
  380.  
  381.             elseif (slider_lo.stype == "bar") then
  382.                 element.slider.min.ele_pos =
  383.                     slider_lo.border + slider_lo.gap
  384.                 element.slider.max.ele_pos =
  385.                     elem_geo.w - (slider_lo.border + slider_lo.gap)
  386.             end
  387.  
  388.             element.slider.min.glob_pos =
  389.                 element.hitbox.x1 + element.slider.min.ele_pos
  390.             element.slider.max.glob_pos =
  391.                 element.hitbox.x1 + element.slider.max.ele_pos
  392.  
  393.             -- -- --
  394.  
  395.             static_ass:draw_start()
  396.  
  397.             -- the box
  398.             static_ass:rect_cw(0, 0, elem_geo.w, elem_geo.h);
  399.  
  400.             -- the "hole"
  401.             static_ass:rect_ccw(slider_lo.border, slider_lo.border,
  402.                 elem_geo.w - slider_lo.border, elem_geo.h - slider_lo.border)
  403.  
  404.             -- marker nibbles
  405.             if not (element.slider.markerF == nil) and (slider_lo.gap > 0) then
  406.                 local markers = element.slider.markerF()
  407.                 for _,marker in pairs(markers) do
  408.                     if (marker > element.slider.min.value) and
  409.                         (marker < element.slider.max.value) then
  410.  
  411.                         local s = get_slider_ele_pos_for(element, marker)
  412.  
  413.                         if (slider_lo.gap > 1) then -- draw triangles
  414.  
  415.                             local a = slider_lo.gap / 0.5 --0.866
  416.  
  417.                             --top
  418.                             if (slider_lo.nibbles_top) then
  419.                                 static_ass:move_to(s - (a/2), slider_lo.border)
  420.                                 static_ass:line_to(s + (a/2), slider_lo.border)
  421.                                 static_ass:line_to(s, foV)
  422.                             end
  423.  
  424.                             --bottom
  425.                             if (slider_lo.nibbles_bottom) then
  426.                                 static_ass:move_to(s - (a/2),
  427.                                     elem_geo.h - slider_lo.border)
  428.                                 static_ass:line_to(s,
  429.                                     elem_geo.h - foV)
  430.                                 static_ass:line_to(s + (a/2),
  431.                                     elem_geo.h - slider_lo.border)
  432.                             end
  433.  
  434.                         else -- draw 1px nibbles
  435.  
  436.                             --top
  437.                             if (slider_lo.nibbles_top) then
  438.                                 static_ass:rect_cw(s - 0.5, slider_lo.gap,
  439.                                     s + 0.5, slider_lo.gap*2);
  440.                             end
  441.  
  442.                             --bottom
  443.                             if (slider_lo.nibbles_bottom) then
  444.                                 static_ass:rect_cw(s - 0.5,
  445.                                     elem_geo.h - slider_lo.gap*2,
  446.                                     s + 0.5,
  447.                                     elem_geo.h - slider_lo.gap);
  448.                             end
  449.                         end
  450.                     end
  451.                 end
  452.             end
  453.         end
  454.  
  455.         element.static_ass = static_ass
  456.  
  457.  
  458.         -- if the element is supposed to be disabled,
  459.         -- style it accordingly and kill the eventresponders
  460.         if not (element.enabled) then
  461.             element.layout.alpha[1] = 136
  462.             element.eventresponder = nil
  463.         end
  464.     end
  465. end
  466.  
  467.  
  468. --
  469. -- Element Rendering
  470. --
  471.  
  472. function render_elements(master_ass)
  473.  
  474.     for n=1, #elements do
  475.         local element = elements[n]
  476.  
  477.         local style_ass = assdraw.ass_new()
  478.         style_ass:merge(element.style_ass)
  479.  
  480.         --alpha
  481.         local ar = element.layout.alpha
  482.         if not (state.animation == nil) then
  483.             ar = {}
  484.             for ai, av in pairs(element.layout.alpha) do
  485.                 ar[ai] = mult_alpha(av, state.animation)
  486.             end
  487.         end
  488.  
  489.         style_ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}",
  490.             ar[1], ar[2], ar[3], ar[4]))
  491.  
  492.         if element.eventresponder and (state.active_element == n) then
  493.  
  494.             -- run render event functions
  495.             if not (element.eventresponder.render == nil) then
  496.                 element.eventresponder.render(element)
  497.             end
  498.  
  499.             if mouse_hit(element) then
  500.                 -- mouse down styling
  501.                 if (element.styledown) then
  502.                     style_ass:append(osc_styles.elementDown)
  503.                 end
  504.  
  505.                 if (element.softrepeat) and (state.mouse_down_counter >= 15
  506.                     and state.mouse_down_counter % 5 == 0) then
  507.  
  508.                     element.eventresponder[state.active_event_source.."_down"](element)
  509.                 end
  510.                 state.mouse_down_counter = state.mouse_down_counter + 1
  511.             end
  512.  
  513.         end
  514.  
  515.         local elem_ass = assdraw.ass_new()
  516.  
  517.         elem_ass:merge(style_ass)
  518.  
  519.         if not (element.type == "button") then
  520.             elem_ass:merge(element.static_ass)
  521.         end
  522.  
  523.  
  524.  
  525.         if (element.type == "slider") then
  526.  
  527.             local slider_lo = element.layout.slider
  528.             local elem_geo = element.layout.geometry
  529.             local s_min, s_max = element.slider.min.value, element.slider.max.value
  530.  
  531.  
  532.             -- draw pos marker
  533.             local pos = element.slider.posF()
  534.  
  535.             if not (pos == nil) then
  536.  
  537.                 local foV = slider_lo.border + slider_lo.gap
  538.                 local foH = 0
  539.                 if (slider_lo.stype == "slider") then
  540.                     foH = elem_geo.h / 2
  541.                 elseif (slider_lo.stype == "bar") then
  542.                     foH = slider_lo.border + slider_lo.gap
  543.                 end
  544.  
  545.                 local xp = get_slider_ele_pos_for(element, pos)
  546.  
  547.                 -- the filling
  548.                 local innerH = elem_geo.h - (2*foV)
  549.  
  550.                 if (slider_lo.stype == "bar") then
  551.                     elem_ass:rect_cw(foH, foV, xp, elem_geo.h - foV)
  552.                 elseif (slider_lo.stype == "slider") then
  553.                     elem_ass:move_to(xp, foV)
  554.                     elem_ass:line_to(xp+(innerH/2), (innerH/2)+foV)
  555.                     elem_ass:line_to(xp, (innerH)+foV)
  556.                     elem_ass:line_to(xp-(innerH/2), (innerH/2)+foV)
  557.                 end
  558.             end
  559.  
  560.             elem_ass:draw_stop()
  561.  
  562.             -- add tooltip
  563.             if not (element.slider.tooltipF == nil) then
  564.  
  565.                 if mouse_hit(element) then
  566.                     local sliderpos = get_slider_value(element)
  567.                     local tooltiplabel = element.slider.tooltipF(sliderpos)
  568.  
  569.                     local an = slider_lo.tooltip_an
  570.  
  571.                     if (slider_lo.adjust_tooltip) then
  572.                         if (sliderpos < (s_min + 5)) then
  573.                             if an == 2 then
  574.                                 an = 1
  575.                             else
  576.                                 an = 7
  577.                             end
  578.                         elseif (sliderpos > (s_max - 5)) then
  579.                             if an == 2 then
  580.                                 an = 3
  581.                             else
  582.                                 an = 9
  583.                             end
  584.                         end
  585.                     end
  586.  
  587.  
  588.                     local ty
  589.                     if (slider_lo.tooltip_an == 2) then
  590.                         ty = element.hitbox.y1 - slider_lo.border
  591.                     else
  592.                         ty = element.hitbox.y2 + slider_lo.border
  593.                     end
  594.  
  595.                     elem_ass:new_event()
  596.                     elem_ass:pos(mp.get_mouse_pos(), ty)
  597.                     elem_ass:an(an)
  598.                     elem_ass:append(slider_lo.tooltip_style)
  599.                     elem_ass:append(tooltiplabel)
  600.  
  601.                 end
  602.             end
  603.  
  604.         elseif (element.type == "button") then
  605.  
  606.             local buttontext
  607.             if type(element.content) == "function" then
  608.                 buttontext = element.content() -- function objects
  609.             elseif not (element.content == nil) then
  610.                 buttontext = element.content -- text objects
  611.             end
  612.  
  613.             local maxchars = element.layout.button.maxchars
  614.  
  615.             if not (maxchars == nil) and (#buttontext > maxchars) then
  616.                 buttontext = string.format("{\\fscx%f}",
  617.                     (maxchars/#buttontext)*100) .. buttontext
  618.             end
  619.  
  620.             elem_ass:append(buttontext)
  621.         end
  622.  
  623.         master_ass:merge(elem_ass)
  624.     end
  625. end
  626.  
  627. --
  628. -- Message display
  629. --
  630.  
  631. function show_message(text, duration)
  632.  
  633.     --print("text: "..text.."   duration: " .. duration)
  634.     if duration == nil then
  635.         duration = tonumber(mp.get_property("options/osd-duration")) / 1000
  636.     elseif not type(duration) == "number" then
  637.         print("duration: " .. duration)
  638.     end
  639.  
  640.     -- cut the text short, otherwise the following functions
  641.     -- may slow down massively on huge input
  642.     text = string.sub(text, 0, 4000)
  643.  
  644.     -- replace actual linebreaks with ASS linebreaks
  645.     -- and get the amount of lines along the way
  646.     local lines
  647.     text, lines = string.gsub(text, "\n", "\\N")
  648.  
  649.     -- append a Zero-Width-Space to . and _ to enable
  650.     -- linebreaking of long filenames
  651.     text = string.gsub(text, "%.", ".\226\128\139")
  652.     text = string.gsub(text, "_", "_\226\128\139")
  653.  
  654.     -- scale the fontsize for longer multi-line output
  655.     local fontsize = tonumber(mp.get_property("options/osd-font-size"))
  656.     local outline = tonumber(mp.get_property("options/osd-border-size"))
  657.  
  658.     if lines > 12 then
  659.         fontsize, outline = fontsize / 2, outline / 1.5
  660.     elseif lines > 8 then
  661.         fontsize, outline = fontsize / 1.5, outline / 1.25
  662.     end
  663.  
  664.     local style = "{\\bord" .. outline .. "\\fs" .. fontsize .. "}"
  665.  
  666.     state.message_text = style .. text
  667.     state.message_timeout = mp.get_time() + duration
  668. end
  669.  
  670. function render_message(ass)
  671.     if not(state.message_timeout == nil) and not(state.message_text == nil)
  672.         and state.message_timeout > mp.get_time() then
  673.  
  674.         ass:new_event()
  675.         ass:append(state.message_text)
  676.     else
  677.         state.message_text = nil
  678.         state.message_timeout = nil
  679.     end
  680. end
  681.  
  682. --
  683. -- Initialisation and Layout
  684. --
  685.  
  686. function new_element(name, type)
  687.     elements[name] = {}
  688.     elements[name].type = type
  689.  
  690.     -- add default stuff
  691.     elements[name].eventresponder = {}
  692.     elements[name].visible = true
  693.     elements[name].enabled = true
  694.     elements[name].softrepeat = false
  695.     elements[name].styledown = (type == "button")
  696.     elements[name].state = {}
  697.  
  698.     if (type == "slider") then
  699.         elements[name].slider = {min = {value = 0}, max = {value = 100}}
  700.     end
  701.  
  702.  
  703.     return elements[name]
  704. end
  705.  
  706. function add_layout(name)
  707.     if not (elements[name] == nil) then
  708.         -- new layout
  709.         elements[name].layout = {}
  710.  
  711.         -- set layout defaults
  712.         elements[name].layout.layer = 50
  713.         elements[name].layout.alpha = {[1] = 0, [2] = 255, [3] = 255, [4] = 255}
  714.  
  715.         if (elements[name].type == "button") then
  716.             elements[name].layout.button = {
  717.                 maxchars = nil,
  718.             }
  719.         elseif (elements[name].type == "slider") then
  720.             -- slider defaults
  721.             elements[name].layout.slider = {
  722.                 border = 1,
  723.                 gap = 1,
  724.                 nibbles_top = true,
  725.                 nibbles_bottom = true,
  726.                 stype = "slider",
  727.                 adjust_tooltip = true,
  728.                 tooltip_style = "",
  729.                 tooltip_an = 2,
  730.             }
  731.         elseif (elements[name].type == "box") then
  732.             elements[name].layout.box = {radius = 0}
  733.         end
  734.  
  735.         return elements[name].layout
  736.     else
  737.         msg.error("Can't add_layout to element \""..name.."\", doesn't exist.")
  738.     end
  739. end
  740.  
  741. --
  742. -- Layouts
  743. --
  744.  
  745. local layouts = {}
  746.  
  747. -- Classic box layout
  748. layouts["box"] = function ()
  749.  
  750.     local osc_geo = {
  751.         w = 550,    -- width
  752.         h = 138,    -- height
  753.         r = 10,     -- corner-radius
  754.         p = 15,     -- padding
  755.     }
  756.  
  757.     -- make sure the OSC actually fits into the video
  758.     if (osc_param.playresx < (osc_geo.w + (2 * osc_geo.p))) then
  759.         osc_param.playresy = (osc_geo.w+(2*osc_geo.p))/osc_param.display_aspect
  760.         osc_param.playresx = osc_param.playresy * osc_param.display_aspect
  761.     end
  762.  
  763.     -- position of the controller according to video aspect and valignment
  764.     local posX = math.floor(get_align(user_opts.halign, osc_param.playresx,
  765.         osc_geo.w, 0))
  766.     local posY = math.floor(get_align(user_opts.valign, osc_param.playresy,
  767.         osc_geo.h, 0))
  768.  
  769.     -- position offset for contents aligned at the borders of the box
  770.     local pos_offsetX = (osc_geo.w - (2*osc_geo.p)) / 2
  771.     local pos_offsetY = (osc_geo.h - (2*osc_geo.p)) / 2
  772.  
  773.     osc_param.areas = {} -- delete areas
  774.  
  775.     -- area for active mouse input
  776.     add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h))
  777.  
  778.     -- area for show/hide
  779.     local sh_area_y0, sh_area_y1
  780.     if user_opts.valign > 0 then
  781.         -- deadzone above OSC
  782.         sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
  783.             posY - (osc_geo.h / 2), 0, 0)
  784.         sh_area_y1 = osc_param.playresy
  785.     else
  786.         -- deadzone below OSC
  787.         sh_area_y0 = 0
  788.         sh_area_y1 = (posY + (osc_geo.h / 2)) +
  789.             get_align(1 - (2*user_opts.deadzonesize),
  790.             osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0)
  791.     end
  792.     add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
  793.  
  794.     -- fetch values
  795.     local osc_w, osc_h, osc_r, osc_p =
  796.         osc_geo.w, osc_geo.h, osc_geo.r, osc_geo.p
  797.  
  798.     local lo
  799.  
  800.     --
  801.     -- Background box
  802.     --
  803.  
  804.     new_element("bgbox", "box")
  805.     lo = add_layout("bgbox")
  806.  
  807.     lo.geometry = {x = posX, y = posY, an = 5, w = osc_w, h = osc_h}
  808.     lo.layer = 10
  809.     lo.style = osc_styles.box
  810.     lo.alpha[1] = user_opts.boxalpha
  811.     lo.alpha[3] = user_opts.boxalpha
  812.     lo.box.radius = osc_r
  813.  
  814.     --
  815.     -- Title row
  816.     --
  817.  
  818.     local titlerowY = posY - pos_offsetY - 10
  819.  
  820.     lo = add_layout("title")
  821.     lo.geometry = {x = posX, y = titlerowY, an = 8, w = 496, h = 12}
  822.     lo.style = osc_styles.vidtitle
  823.     lo.button.maxchars = 90
  824.  
  825.     lo = add_layout("pl_prev")
  826.     lo.geometry =
  827.         {x = (posX - pos_offsetX), y = titlerowY, an = 7, w = 12, h = 12}
  828.     lo.style = osc_styles.topButtons
  829.  
  830.     lo = add_layout("pl_next")
  831.     lo.geometry =
  832.         {x = (posX + pos_offsetX), y = titlerowY, an = 9, w = 12, h = 12}
  833.     lo.style = osc_styles.topButtons
  834.  
  835.     --
  836.     -- Big buttons
  837.     --
  838.  
  839.     local bigbtnrowY = posY - pos_offsetY + 35
  840.     local bigbtndist = 60
  841.  
  842.     lo = add_layout("playpause")
  843.     lo.geometry =
  844.         {x = posX, y = bigbtnrowY, an = 5, w = 40, h = 40}
  845.     lo.style = osc_styles.bigButtons
  846.  
  847.     lo = add_layout("skipback")
  848.     lo.geometry =
  849.         {x = posX - bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40}
  850.     lo.style = osc_styles.bigButtons
  851.  
  852.     lo = add_layout("skipfrwd")
  853.     lo.geometry =
  854.         {x = posX + bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40}
  855.     lo.style = osc_styles.bigButtons
  856.  
  857.     lo = add_layout("ch_prev")
  858.     lo.geometry =
  859.         {x = posX - (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40}
  860.     lo.style = osc_styles.bigButtons
  861.  
  862.     lo = add_layout("ch_next")
  863.     lo.geometry =
  864.         {x = posX + (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40}
  865.     lo.style = osc_styles.bigButtons
  866.  
  867.     lo = add_layout("cy_audio")
  868.     lo.geometry =
  869.         {x = posX - pos_offsetX, y = bigbtnrowY, an = 1, w = 70, h = 18}
  870.     lo.style = osc_styles.smallButtonsL
  871.  
  872.     lo = add_layout("cy_sub")
  873.     lo.geometry =
  874.         {x = posX - pos_offsetX, y = bigbtnrowY, an = 7, w = 70, h = 18}
  875.     lo.style = osc_styles.smallButtonsL
  876.  
  877.     lo = add_layout("tog_fs")
  878.     lo.geometry =
  879.         {x = posX+pos_offsetX, y = bigbtnrowY, an = 6, w = 25, h = 25}
  880.     lo.style = osc_styles.smallButtonsR
  881.  
  882.     --
  883.     -- Seekbar
  884.     --
  885.  
  886.     lo = add_layout("seekbar")
  887.     lo.geometry =
  888.         {x = posX, y = posY+pos_offsetY-22, an = 2, w = pos_offsetX*2, h = 15}
  889.     lo.style = osc_styles.timecodes
  890.     lo.slider.tooltip_style = osc_styles.vidtitle
  891.     lo.slider.stype = user_opts["seekbarstyle"]
  892.  
  893.     --
  894.     -- Timecodes + Cache
  895.     --
  896.  
  897.     local bottomrowY = posY + pos_offsetY - 5
  898.  
  899.     lo = add_layout("tc_left")
  900.     lo.geometry =
  901.         {x = posX - pos_offsetX, y = bottomrowY, an = 4, w = 110, h = 18}
  902.     lo.style = osc_styles.timecodes
  903.  
  904.     lo = add_layout("tc_right")
  905.     lo.geometry =
  906.         {x = posX + pos_offsetX, y = bottomrowY, an = 6, w = 110, h = 18}
  907.     lo.style = osc_styles.timecodes
  908.  
  909.     lo = add_layout("cache")
  910.     lo.geometry =
  911.         {x = posX, y = bottomrowY, an = 5, w = 110, h = 18}
  912.     lo.style = osc_styles.timecodes
  913.  
  914. end
  915.  
  916. -- slim box layout
  917. layouts["slimbox"] = function ()
  918.  
  919.     local osc_geo = {
  920.         w = 660,    -- width
  921.         h = 70,     -- height
  922.         r = 10,     -- corner-radius
  923.     }
  924.  
  925.     -- make sure the OSC actually fits into the video
  926.     if (osc_param.playresx < (osc_geo.w)) then
  927.         osc_param.playresy = (osc_geo.w)/osc_param.display_aspect
  928.         osc_param.playresx = osc_param.playresy * osc_param.display_aspect
  929.     end
  930.  
  931.     -- position of the controller according to video aspect and valignment
  932.     local posX = math.floor(get_align(user_opts.halign, osc_param.playresx,
  933.         osc_geo.w, 0))
  934.     local posY = math.floor(get_align(user_opts.valign, osc_param.playresy,
  935.         osc_geo.h, 0))
  936.  
  937.     osc_param.areas = {} -- delete areas
  938.  
  939.     -- area for active mouse input
  940.     add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h))
  941.  
  942.     -- area for show/hide
  943.     local sh_area_y0, sh_area_y1
  944.     if user_opts.valign > 0 then
  945.         -- deadzone above OSC
  946.         sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
  947.             posY - (osc_geo.h / 2), 0, 0)
  948.         sh_area_y1 = osc_param.playresy
  949.     else
  950.         -- deadzone below OSC
  951.         sh_area_y0 = 0
  952.         sh_area_y1 = (posY + (osc_geo.h / 2)) +
  953.             get_align(1 - (2*user_opts.deadzonesize),
  954.             osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0)
  955.     end
  956.     add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
  957.  
  958.     local lo
  959.  
  960.     local tc_w, ele_h, inner_w = 100, 20, osc_geo.w - 100
  961.  
  962.     -- styles
  963.     local styles = {
  964.         box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}",
  965.         timecodes = "{\\1c&HFFFFFF\\3c&H000000\\fs20\\bord2\\blur1}",
  966.         tooltip = "{\\1c&HFFFFFF\\3c&H000000\\fs12\\bord1\\blur0.5}",
  967.     }
  968.  
  969.  
  970.     new_element("bgbox", "box")
  971.     lo = add_layout("bgbox")
  972.  
  973.     lo.geometry = {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h}
  974.     lo.layer = 10
  975.     lo.style = osc_styles.box
  976.     lo.alpha[1] = user_opts.boxalpha
  977.     lo.alpha[3] = 0
  978.     lo.box.radius = osc_geo.r
  979.  
  980.  
  981.     lo = add_layout("seekbar")
  982.     lo.geometry =
  983.         {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h}
  984.     lo.style = osc_styles.timecodes
  985.     lo.slider.border = 0
  986.     lo.slider.gap = 1.5
  987.     lo.slider.tooltip_style = styles.tooltip
  988.     lo.slider.stype = user_opts["seekbarstyle"]
  989.     lo.slider.adjust_tooltip = false
  990.  
  991.     --
  992.     -- Timecodes
  993.     --
  994.  
  995.     lo = add_layout("tc_left")
  996.     lo.geometry =
  997.         {x = posX - (inner_w/2) + osc_geo.r, y = posY + 1,
  998.         an = 7, w = tc_w, h = ele_h}
  999.     lo.style = styles.timecodes
  1000.     lo.alpha[3] = user_opts.boxalpha
  1001.  
  1002.     lo = add_layout("tc_right")
  1003.     lo.geometry =
  1004.         {x = posX + (inner_w/2) - osc_geo.r, y = posY + 1,
  1005.         an = 9, w = tc_w, h = ele_h}
  1006.     lo.style = styles.timecodes
  1007.     lo.alpha[3] = user_opts.boxalpha
  1008.  
  1009.     -- Cache
  1010.  
  1011.     lo = add_layout("cache")
  1012.     lo.geometry =
  1013.         {x = posX, y = posY + 1,
  1014.         an = 8, w = tc_w, h = ele_h}
  1015.     lo.style = styles.timecodes
  1016.     lo.alpha[3] = user_opts.boxalpha
  1017.  
  1018.  
  1019. end
  1020.  
  1021. layouts["bottombar"] = function()
  1022.     local osc_geo = {
  1023.         x = -2,
  1024.         y = osc_param.playresy - 36,
  1025.         an = 7,
  1026.         w = osc_param.playresx + 4,
  1027.         h = 38,
  1028.     }
  1029.  
  1030.     local padX = 6
  1031.     local padY = 2
  1032.  
  1033.     osc_param.areas = {}
  1034.  
  1035.     add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an,
  1036.                                         osc_geo.w, osc_geo.h))
  1037.  
  1038.     local sh_area_y0, sh_area_y1
  1039.     sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
  1040.                            osc_geo.y - (osc_geo.h / 2), 0, 0)
  1041.     sh_area_y1 = osc_param.playresy
  1042.     add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
  1043.  
  1044.     local lo, geo
  1045.  
  1046.     -- Background bar
  1047.     new_element("bgbox", "box")
  1048.     lo = add_layout("bgbox")
  1049.  
  1050.     lo.geometry = osc_geo
  1051.     lo.layer = 10
  1052.     lo.style = osc_styles.box
  1053.     lo.alpha[1] = user_opts.boxalpha
  1054.  
  1055.  
  1056.     -- Playlist prev/next
  1057.     geo = { x = osc_geo.x + padX, y = osc_geo.y + padY, an = 7, w = 12, h = 12 }
  1058.     lo = add_layout("pl_prev")
  1059.     lo.geometry = geo
  1060.     lo.style = osc_styles.topButtons
  1061.  
  1062.     geo = { x = geo.x + geo.w + padX, y = geo.y, an = 7, w = 12, h = 12 }
  1063.     lo = add_layout("pl_next")
  1064.     lo.geometry = geo
  1065.     lo.style = osc_styles.topButtons
  1066.  
  1067.     -- Title
  1068.     geo = { x = geo.x + geo.w + padX, y = geo.y, an = 7, w = 1000, h = 12 }
  1069.     lo = add_layout("title")
  1070.     lo.geometry = geo
  1071.     lo.style = osc_styles.vidtitle
  1072.  
  1073.     -- Cache
  1074.     geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y, an = 9,
  1075.             w = 100, h = 12 }
  1076.     lo = add_layout("cache")
  1077.     lo.geometry = geo
  1078.     lo.style = osc_styles.vidtitle
  1079.  
  1080.  
  1081.     -- Playback control buttons
  1082.     geo = { x = osc_geo.x + padX, y = geo.y + geo.h + padY, an = 7,
  1083.             w = 18, h = 18 }
  1084.     lo = add_layout("playpause")
  1085.     lo.geometry = geo
  1086.     lo.style = osc_styles.smallButtonsL
  1087.  
  1088.     geo = { x = geo.x + geo.w + padX, y = geo.y, an = 7, w = geo.w, h = geo.h }
  1089.     lo = add_layout("ch_prev")
  1090.     lo.geometry = geo
  1091.     lo.style = osc_styles.smallButtonsL
  1092.  
  1093.     geo = { x = geo.x + geo.w + padX, y = geo.y, an = 7, w = geo.w, h = geo.h }
  1094.     lo = add_layout("ch_next")
  1095.     lo.geometry = geo
  1096.     lo.style = osc_styles.smallButtonsL
  1097.  
  1098.  
  1099.     -- Left timecode
  1100.     geo = { x = geo.x + geo.w + padX + 100, y = geo.y, an = 9,
  1101.             w = 100, h = geo.h }
  1102.     lo = add_layout("tc_left")
  1103.     lo.geometry = geo
  1104.     lo.style = osc_styles.timecodes
  1105.  
  1106.     local sb_l = geo.x + padX
  1107.  
  1108.  
  1109.     -- Track selection buttons
  1110.     geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y, an = 9,
  1111.             w = 60, h = geo.h }
  1112.     lo = add_layout("cy_sub")
  1113.     lo.geometry = geo
  1114.     lo.style = osc_styles.smallButtonsL
  1115.  
  1116.     geo = { x = geo.x - geo.w - padX, y = geo.y, an = 9, w = geo.w, h = geo.h }
  1117.     lo = add_layout("cy_audio")
  1118.     lo.geometry = geo
  1119.     lo.style = osc_styles.smallButtonsL
  1120.  
  1121.  
  1122.     -- Right timecode
  1123.     geo = { x = geo.x - geo.w - padX - 100, y = geo.y, an = 7,
  1124.             w = 100, h = geo.h }
  1125.     lo = add_layout("tc_right")
  1126.     lo.geometry = geo
  1127.     lo.style = osc_styles.timecodes
  1128.  
  1129.     local sb_r = geo.x - padX
  1130.  
  1131.  
  1132.     -- Seekbar
  1133.     geo = {x = sb_l, y = osc_param.playresy, an = 1, w = sb_r - sb_l, h = geo.h}
  1134.     new_element("bgbar1", "box")
  1135.     lo = add_layout("bgbar1")
  1136.  
  1137.     lo.geometry = geo
  1138.     lo.layer = 15
  1139.     lo.style = osc_styles.timecodes
  1140.     lo.alpha[1] = math.min(255, user_opts.boxalpha + 140)
  1141.  
  1142.     lo = add_layout("seekbar")
  1143.     lo.geometry = geo
  1144.     lo.style = osc_styles.timecodes
  1145.     lo.layer = 16
  1146.     lo.slider.border = 0
  1147.     lo.slider.tooltip_style = osc_styles.vidtitle
  1148.     lo.slider.stype = user_opts["seekbarstyle"]
  1149. end
  1150.  
  1151. layouts["topbar"] = function()
  1152.     local osc_geo = {
  1153.         x = -2,
  1154.         y = 38,
  1155.         an = 1,
  1156.         w = osc_param.playresx + 4,
  1157.         h = 38,
  1158.     }
  1159.  
  1160.     local padX = 6
  1161.     local padY = 2
  1162.  
  1163.     osc_param.areas = {}
  1164.  
  1165.     add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an,
  1166.                                         osc_geo.w, osc_geo.h))
  1167.  
  1168.     local sh_area_y0, sh_area_y1
  1169.     sh_area_y0 = 0
  1170.     sh_area_y1 = (osc_geo.y + (osc_geo.h / 2)) +
  1171.                  get_align(1 - (2*user_opts.deadzonesize),
  1172.                  osc_param.playresy - (osc_geo.y + (osc_geo.h / 2)), 0, 0)
  1173.     add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
  1174.  
  1175.     local lo, geo
  1176.  
  1177.     -- Background bar
  1178.     new_element("bgbox", "box")
  1179.     lo = add_layout("bgbox")
  1180.  
  1181.     lo.geometry = osc_geo
  1182.     lo.layer = 10
  1183.     lo.style = osc_styles.box
  1184.     lo.alpha[1] = user_opts.boxalpha
  1185.  
  1186.  
  1187.     -- Playback control buttons
  1188.     geo = { x = osc_geo.x + padX, y = padY + 18, an = 1,
  1189.             w = 18, h = 18 }
  1190.     lo = add_layout("playpause")
  1191.     lo.geometry = geo
  1192.     lo.style = osc_styles.smallButtonsL
  1193.  
  1194.     geo = { x = geo.x + geo.w + padX, y = geo.y, an = 1, w = geo.w, h = geo.h }
  1195.     lo = add_layout("ch_prev")
  1196.     lo.geometry = geo
  1197.     lo.style = osc_styles.smallButtonsL
  1198.  
  1199.     geo = { x = geo.x + geo.w + padX, y = geo.y, an = 1, w = geo.w, h = geo.h }
  1200.     lo = add_layout("ch_next")
  1201.     lo.geometry = geo
  1202.     lo.style = osc_styles.smallButtonsL
  1203.  
  1204.  
  1205.     -- Left timecode
  1206.     geo = { x = geo.x + geo.w + padX + 100, y = geo.y, an = 3,
  1207.             w = 100, h = geo.h }
  1208.     lo = add_layout("tc_left")
  1209.     lo.geometry = geo
  1210.     lo.style = osc_styles.timecodes
  1211.  
  1212.     local sb_l = geo.x + padX
  1213.  
  1214.  
  1215.     -- Track selection buttons
  1216.     geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y, an = 3,
  1217.             w = 60, h = geo.h }
  1218.     lo = add_layout("cy_sub")
  1219.     lo.geometry = geo
  1220.     lo.style = osc_styles.smallButtonsL
  1221.  
  1222.     geo = { x = geo.x - geo.w - padX, y = geo.y, an = 3, w = geo.w, h = geo.h }
  1223.     lo = add_layout("cy_audio")
  1224.     lo.geometry = geo
  1225.     lo.style = osc_styles.smallButtonsL
  1226.  
  1227.  
  1228.     -- Right timecode
  1229.     geo = { x = geo.x - geo.w - padX - 100, y = geo.y, an = 1,
  1230.             w = 100, h = geo.h }
  1231.     lo = add_layout("tc_right")
  1232.     lo.geometry = geo
  1233.     lo.style = osc_styles.timecodes
  1234.  
  1235.     local sb_r = geo.x - padX
  1236.  
  1237.  
  1238.     -- Seekbar
  1239.     geo = { x = sb_l, y = 0, an = 7, w = sb_r - sb_l, h = geo.h }
  1240.     new_element("bgbar1", "box")
  1241.     lo = add_layout("bgbar1")
  1242.  
  1243.     lo.geometry = geo
  1244.     lo.layer = 15
  1245.     lo.style = osc_styles.timecodes
  1246.     lo.alpha[1] = math.min(255, user_opts.boxalpha + 140)
  1247.  
  1248.     lo = add_layout("seekbar")
  1249.     lo.geometry = geo
  1250.     lo.style = osc_styles.timecodes
  1251.     lo.layer = 16
  1252.     lo.slider.border = 0
  1253.     lo.slider.tooltip_style = osc_styles.vidtitle
  1254.     lo.slider.stype = user_opts["seekbarstyle"]
  1255.     lo.slider.tooltip_an = 8
  1256.  
  1257.  
  1258.     -- Playlist prev/next
  1259.     geo = { x = osc_geo.x + padX, y = osc_geo.h - padY, an = 1, w = 12, h = 12 }
  1260.     lo = add_layout("pl_prev")
  1261.     lo.geometry = geo
  1262.     lo.style = osc_styles.topButtons
  1263.  
  1264.     geo = { x = geo.x + geo.w + padX, y = geo.y, an = 1, w = 12, h = 12 }
  1265.     lo = add_layout("pl_next")
  1266.     lo.geometry = geo
  1267.     lo.style = osc_styles.topButtons
  1268.  
  1269.     -- Title
  1270.     geo = { x = geo.x + geo.w + padX, y = geo.y, an = 1, w = 1000, h = 12 }
  1271.     lo = add_layout("title")
  1272.     lo.geometry = geo
  1273.     lo.style = osc_styles.vidtitle
  1274.  
  1275.     -- Cache
  1276.     geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y, an = 3,
  1277.             w = 100, h = 12 }
  1278.     lo = add_layout("cache")
  1279.     lo.geometry = geo
  1280.     lo.style = osc_styles.vidtitle
  1281. end
  1282.  
  1283. -- Validate string type user options
  1284. function validate_user_opts()
  1285.     if layouts[user_opts.layout] == nil then
  1286.         msg.warn("Invalid setting \""..user_opts.layout.."\" for layout")
  1287.         user_opts.layout = "box"
  1288.     end
  1289.  
  1290.     if user_opts.seekbarstyle ~= "slider" and
  1291.        user_opts.seekbarstyle ~= "bar" then
  1292.         msg.warn("Invalid setting \"" .. user_opts.seekbarstyle
  1293.             .. "\" for seekbarstyle")
  1294.         user_opts.seekbarstyle = "slider"
  1295.     end
  1296. end
  1297.  
  1298.  
  1299. -- OSC INIT
  1300. function osc_init()
  1301.     msg.debug("osc_init")
  1302.  
  1303.     -- set canvas resolution according to display aspect and scaling setting
  1304.     local baseResY = 720
  1305.     local display_w, display_h, display_aspect = mp.get_screen_size()
  1306.     local scale = 1
  1307.  
  1308.     if (mp.get_property("video") == "no") then -- dummy/forced window
  1309.         scale = user_opts.scaleforcedwindow
  1310.     elseif state.fullscreen then
  1311.         scale = user_opts.scalefullscreen
  1312.     else
  1313.         scale = user_opts.scalewindowed
  1314.     end
  1315.  
  1316.     if user_opts.vidscale then
  1317.         osc_param.playresy = baseResY / scale
  1318.     else
  1319.         osc_param.playresy = display_h / scale
  1320.     end
  1321.     osc_param.playresx = osc_param.playresy * display_aspect
  1322.     osc_param.display_aspect = display_aspect
  1323.  
  1324.  
  1325.  
  1326.  
  1327.  
  1328.     elements = {}
  1329.  
  1330.     -- some often needed stuff
  1331.     local pl_count = mp.get_property_number("playlist-count")
  1332.     local have_pl = (pl_count > 1)
  1333.     local have_ch = (mp.get_property_number("chapters", 0) > 0)
  1334.  
  1335.     local ne
  1336.  
  1337.     -- title
  1338.     ne = new_element("title", "button")
  1339.  
  1340.     ne.content = function ()
  1341.         local title = mp.get_property_osd("media-title")
  1342.         if not (title == nil) then
  1343.             return (title)
  1344.         else
  1345.             return ("mpv")
  1346.         end
  1347.     end
  1348.  
  1349.     ne.eventresponder["mouse_btn0_up"] = function ()
  1350.         local title = mp.get_property_osd("media-title")
  1351.         if (have_pl) then
  1352.             local pl_pos = countone(mp.get_property_number("playlist-pos"))
  1353.             title = "[" .. pl_pos .. "/" .. pl_count .. "] " .. title
  1354.         end
  1355.         show_message(title)
  1356.     end
  1357.  
  1358.     ne.eventresponder["mouse_btn2_up"] =
  1359.         function () show_message(mp.get_property_osd("filename")) end
  1360.  
  1361.     -- playlist buttons
  1362.  
  1363.     -- prev
  1364.     ne = new_element("pl_prev", "button")
  1365.  
  1366.     ne.content = "\238\132\144"
  1367.     ne.visible = have_pl
  1368.     ne.eventresponder["mouse_btn0_up"] =
  1369.         function () mp.commandv("playlist_prev", "weak") end
  1370.     ne.eventresponder["shift+mouse_btn0_up"] =
  1371.         function () show_message(mp.get_property_osd("playlist"), 3) end
  1372.  
  1373.     --next
  1374.     ne = new_element("pl_next", "button")
  1375.  
  1376.     ne.content = "\238\132\129"
  1377.     ne.visible = have_pl
  1378.     ne.eventresponder["mouse_btn0_up"] =
  1379.         function () mp.commandv("playlist_next", "weak") end
  1380.     ne.eventresponder["shift+mouse_btn0_up"] =
  1381.         function () show_message(mp.get_property_osd("playlist"), 3) end
  1382.  
  1383.  
  1384.     -- big buttons
  1385.  
  1386.     --playpause
  1387.     ne = new_element("playpause", "button")
  1388.  
  1389.     ne.content = function ()
  1390.         if mp.get_property("pause") == "yes" then
  1391.             return ("\238\132\129")
  1392.         else
  1393.             return ("\238\128\130")
  1394.         end
  1395.     end
  1396.     ne.eventresponder["mouse_btn0_up"] =
  1397.         function () mp.commandv("cycle", "pause") end
  1398.  
  1399.     --skipback
  1400.     ne = new_element("skipback", "button")
  1401.  
  1402.     ne.softrepeat = true
  1403.     ne.content = "\238\128\132"
  1404.     ne.eventresponder["mouse_btn0_down"] =
  1405.         function () mp.commandv("seek", -5, "relative", "keyframes") end
  1406.     ne.eventresponder["shift+mouse_btn0_down"] =
  1407.         function () mp.commandv("frame_back_step") end
  1408.     ne.eventresponder["mouse_btn2_down"] =
  1409.         function () mp.commandv("seek", -30, "relative", "keyframes") end
  1410.  
  1411.     --skipfrwd
  1412.     ne = new_element("skipfrwd", "button")
  1413.  
  1414.     ne.softrepeat = true
  1415.     ne.content = "\238\128\133"
  1416.     ne.eventresponder["mouse_btn0_down"] =
  1417.         function () mp.commandv("seek", 10, "relative", "keyframes") end
  1418.     ne.eventresponder["shift+mouse_btn0_down"] =
  1419.         function () mp.commandv("frame_step") end
  1420.     ne.eventresponder["mouse_btn2_down"] =
  1421.         function () mp.commandv("seek", 60, "relative", "keyframes") end
  1422.  
  1423.     --ch_prev
  1424.     ne = new_element("ch_prev", "button")
  1425.  
  1426.     ne.enabled = have_ch
  1427.     ne.content = "\238\132\132"
  1428.     ne.eventresponder["mouse_btn0_up"] =
  1429.         function () mp.commandv("osd-msg", "add", "chapter", -1) end
  1430.     ne.eventresponder["shift+mouse_btn0_up"] =
  1431.         function () show_message(mp.get_property_osd("chapter-list"), 3) end
  1432.  
  1433.     --ch_next
  1434.     ne = new_element("ch_next", "button")
  1435.  
  1436.     ne.enabled = have_ch
  1437.     ne.content = "\238\132\133"
  1438.     ne.eventresponder["mouse_btn0_up"] =
  1439.         function () mp.commandv("osd-msg", "add", "chapter", 1) end
  1440.     ne.eventresponder["shift+mouse_btn0_up"] =
  1441.         function () show_message(mp.get_property_osd("chapter-list"), 3) end
  1442.  
  1443.     --
  1444.     update_tracklist()
  1445.  
  1446.     --cy_audio
  1447.     ne = new_element("cy_audio", "button")
  1448.  
  1449.     ne.enabled = (#tracks_osc.audio > 0)
  1450.     ne.content = function ()
  1451.         local aid = "–"
  1452.         if not (get_track("audio") == 0) then
  1453.             aid = get_track("audio")
  1454.         end
  1455.         return ("\238\132\134" .. osc_styles.smallButtonsLlabel
  1456.             .. " " .. aid .. "/" .. #tracks_osc.audio)
  1457.     end
  1458.     ne.eventresponder["mouse_btn0_up"] =
  1459.         function () set_track("audio", 1) end
  1460.     ne.eventresponder["mouse_btn2_up"] =
  1461.         function () set_track("audio", -1) end
  1462.     ne.eventresponder["shift+mouse_btn0_down"] =
  1463.         function () show_message(get_tracklist("audio"), 2) end
  1464.  
  1465.     --cy_sub
  1466.     ne = new_element("cy_sub", "button")
  1467.  
  1468.     ne.enabled = (#tracks_osc.sub > 0)
  1469.     ne.content = function ()
  1470.         local sid = "–"
  1471.         if not (get_track("sub") == 0) then
  1472.             sid = get_track("sub")
  1473.         end
  1474.         return ("\238\132\135" .. osc_styles.smallButtonsLlabel
  1475.             .. " " .. sid .. "/" .. #tracks_osc.sub)
  1476.     end
  1477.     ne.eventresponder["mouse_btn0_up"] =
  1478.         function () set_track("sub", 1) end
  1479.     ne.eventresponder["mouse_btn2_up"] =
  1480.         function () set_track("sub", -1) end
  1481.     ne.eventresponder["shift+mouse_btn0_down"] =
  1482.         function () show_message(get_tracklist("sub"), 2) end
  1483.  
  1484.     --tog_fs
  1485.     ne = new_element("tog_fs", "button")
  1486.     ne.content = function ()
  1487.         if (state.fullscreen) then
  1488.             return ("\238\132\137")
  1489.         else
  1490.             return ("\238\132\136")
  1491.         end
  1492.     end
  1493.     ne.eventresponder["mouse_btn0_up"] =
  1494.         function () mp.commandv("cycle", "fullscreen") end
  1495.  
  1496.  
  1497.     --seekbar
  1498.     ne = new_element("seekbar", "slider")
  1499.  
  1500.     ne.enabled = not (mp.get_property("percent-pos") == nil)
  1501.     ne.slider.markerF = function ()
  1502.         local duration = mp.get_property_number("duration", nil)
  1503.         if not (duration == nil) then
  1504.             local chapters = mp.get_property_native("chapter-list", {})
  1505.             local markers = {}
  1506.             for n = 1, #chapters do
  1507.                 markers[n] = (chapters[n].time / duration * 100)
  1508.             end
  1509.             return markers
  1510.         else
  1511.             return {}
  1512.         end
  1513.     end
  1514.     ne.slider.posF =
  1515.         function () return mp.get_property_number("percent-pos", nil) end
  1516.     ne.slider.tooltipF = function (pos)
  1517.         local duration = mp.get_property_number("duration", nil)
  1518.         if not ((duration == nil) or (pos == nil)) then
  1519.             possec = duration * (pos / 100)
  1520.             return mp.format_time(possec)
  1521.         else
  1522.             return ""
  1523.         end
  1524.     end
  1525.     ne.eventresponder["mouse_move"] = --keyframe seeking when mouse is dragged
  1526.         function (element)
  1527.             -- mouse move events may pile up during seeking and may still get
  1528.             -- sent when the user is done seeking, so we need to throw away
  1529.             -- identical seeks
  1530.             local seekto = get_slider_value(element)
  1531.             if (element.state.lastseek == nil) or
  1532.                 (not (element.state.lastseek == seekto)) then
  1533.                     mp.commandv("seek", seekto,
  1534.                         "absolute-percent", "keyframes")
  1535.                     element.state.lastseek = seekto
  1536.             end
  1537.  
  1538.         end
  1539.     ne.eventresponder["mouse_btn0_down"] = --exact seeks on single clicks
  1540.         function (element) mp.commandv("seek", get_slider_value(element),
  1541.             "absolute-percent", "exact") end
  1542.     ne.eventresponder["reset"] =
  1543.         function (element) element.state.lastseek = nil end
  1544.  
  1545.  
  1546.     -- tc_left (current pos)
  1547.     ne = new_element("tc_left", "button")
  1548.  
  1549.     ne.content = function ()
  1550.         if (state.tc_ms) then
  1551.             return (mp.get_property_osd("playback-time/full"))
  1552.         else
  1553.             return (mp.get_property_osd("playback-time"))
  1554.         end
  1555.     end
  1556.     ne.eventresponder["mouse_btn0_up"] =
  1557.         function () state.tc_ms = not state.tc_ms end
  1558.  
  1559.     -- tc_right (total/remaining time)
  1560.     ne = new_element("tc_right", "button")
  1561.  
  1562.     ne.visible = (not (mp.get_property("duration") == nil))
  1563.         and (mp.get_property_number("duration") > 0)
  1564.     ne.content = function ()
  1565.         if (state.rightTC_trem) then
  1566.             if state.tc_ms then
  1567.                 return ("-"..mp.get_property_osd("playtime-remaining/full"))
  1568.             else
  1569.                 return ("-"..mp.get_property_osd("playtime-remaining"))
  1570.             end
  1571.         else
  1572.             if state.tc_ms then
  1573.                 return (mp.get_property_osd("length/full"))
  1574.             else
  1575.                 return (mp.get_property_osd("duration"))
  1576.             end
  1577.         end
  1578.     end
  1579.     ne.eventresponder["mouse_btn0_up"] =
  1580.         function () state.rightTC_trem = not state.rightTC_trem end
  1581.  
  1582.     -- cache
  1583.     ne = new_element("cache", "button")
  1584.  
  1585.     ne.content = function ()
  1586.         local dmx_cache = mp.get_property_number("demuxer-cache-duration")
  1587.         if not (dmx_cache == nil) then
  1588.             dmx_cache = math.floor(dmx_cache + 0.5) .. "s + "
  1589.         else
  1590.             dmx_cache = ""
  1591.         end
  1592.         local cache_used = mp.get_property_number("cache-used")
  1593.         if not (cache_used == nil) then
  1594.             if (cache_used < 1024) then
  1595.                 cache_used = cache_used .. " KB"
  1596.             else
  1597.                 cache_used = math.floor((cache_used/102.4)+0.5)/10 .. " MB"
  1598.             end
  1599.             return ("Cache: " .. dmx_cache .. cache_used)
  1600.         else
  1601.             return ""
  1602.         end
  1603.     end
  1604.  
  1605.  
  1606.  
  1607.     -- load layout
  1608.     layouts[user_opts.layout]()
  1609.  
  1610.     --do something with the elements
  1611.     prepare_elements()
  1612.  
  1613. end
  1614.  
  1615.  
  1616.  
  1617. --
  1618. -- Other important stuff
  1619. --
  1620.  
  1621.  
  1622. function show_osc()
  1623.     msg.debug("show_osc")
  1624.     --remember last time of invocation (mouse move)
  1625.     state.showtime = mp.get_time()
  1626.  
  1627.     osc_visible(true)
  1628.  
  1629.     if (user_opts.fadeduration > 0) then
  1630.         state.anitype = nil
  1631.     end
  1632.  
  1633. end
  1634.  
  1635. function hide_osc()
  1636.     msg.debug("hide_osc")
  1637.     if (user_opts.fadeduration > 0) then
  1638.         if not(state.osc_visible == false) then
  1639.             state.anitype = "out"
  1640.             control_timer()
  1641.         end
  1642.     else
  1643.         osc_visible(false)
  1644.     end
  1645. end
  1646.  
  1647. function osc_visible(visible)
  1648.     state.osc_visible = visible
  1649.     control_timer()
  1650. end
  1651.  
  1652. function pause_state(name, enabled)
  1653.     state.paused = enabled
  1654.     control_timer()
  1655. end
  1656.  
  1657. function cache_state(name, idle)
  1658.     state.cache_idle = idle
  1659.     control_timer()
  1660. end
  1661.  
  1662. function control_timer()
  1663.     if (state.paused) and (state.osc_visible) and
  1664.         ( not(state.cache_idle) or not (state.anitype == nil) ) then
  1665.  
  1666.         timer_start()
  1667.     else
  1668.         timer_stop()
  1669.     end
  1670. end
  1671.  
  1672. function timer_start()
  1673.     if not (state.timer_active) then
  1674.         msg.debug("timer start")
  1675.  
  1676.         if (state.timer == nil) then
  1677.             -- create new timer
  1678.             state.timer = mp.add_periodic_timer(0.03, tick)
  1679.         else
  1680.             -- resume existing one
  1681.             state.timer:resume()
  1682.         end
  1683.  
  1684.         state.timer_active = true
  1685.     end
  1686. end
  1687.  
  1688. function timer_stop()
  1689.     if (state.timer_active) then
  1690.         msg.debug("timer stop")
  1691.  
  1692.         if not (state.timer == nil) then
  1693.             -- kill timer
  1694.             state.timer:kill()
  1695.         end
  1696.  
  1697.         state.timer_active = false
  1698.     end
  1699. end
  1700.  
  1701.  
  1702.  
  1703. function mouse_leave()
  1704.     hide_osc()
  1705.     -- reset mouse position
  1706.     state.last_mouseX, state.last_mouseY = nil, nil
  1707. end
  1708.  
  1709. function request_init()
  1710.     state.initREQ = true
  1711. end
  1712.  
  1713. function render()
  1714.     msg.debug("rendering")
  1715.     local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_screen_size()
  1716.     local mouseX, mouseY = mp.get_mouse_pos()
  1717.     local now = mp.get_time()
  1718.  
  1719.     -- check if display changed, if so request reinit
  1720.     if not (state.mp_screen_sizeX == current_screen_sizeX
  1721.         and state.mp_screen_sizeY == current_screen_sizeY) then
  1722.  
  1723.         request_init()
  1724.  
  1725.         state.mp_screen_sizeX = current_screen_sizeX
  1726.         state.mp_screen_sizeY = current_screen_sizeY
  1727.     end
  1728.  
  1729.     -- init management
  1730.     if state.initREQ then
  1731.         osc_init()
  1732.         state.initREQ = false
  1733.  
  1734.         -- store initial mouse position
  1735.         if (state.last_mouseX == nil or state.last_mouseY == nil)
  1736.             and not (mouseX == nil or mouseY == nil) then
  1737.  
  1738.             state.last_mouseX, state.last_mouseY = mouseX, mouseY
  1739.         end
  1740.     end
  1741.  
  1742.  
  1743.     -- fade animation
  1744.     if not(state.anitype == nil) then
  1745.  
  1746.         if (state.anistart == nil) then
  1747.             state.anistart = now
  1748.         end
  1749.  
  1750.         if (now < state.anistart + (user_opts.fadeduration/1000)) then
  1751.  
  1752.             if (state.anitype == "in") then --fade in
  1753.                 osc_visible(true)
  1754.                 state.animation = scale_value(state.anistart,
  1755.                     (state.anistart + (user_opts.fadeduration/1000)),
  1756.                     255, 0, now)
  1757.             elseif (state.anitype == "out") then --fade out
  1758.                 state.animation = scale_value(state.anistart,
  1759.                     (state.anistart + (user_opts.fadeduration/1000)),
  1760.                     0, 255, now)
  1761.             end
  1762.  
  1763.         else
  1764.             if (state.anitype == "out") then
  1765.                 osc_visible(false)
  1766.             end
  1767.             state.anistart = nil
  1768.             state.animation = nil
  1769.             state.anitype =  nil
  1770.         end
  1771.     else
  1772.         state.anistart = nil
  1773.         state.animation = nil
  1774.         state.anitype =  nil
  1775.     end
  1776.  
  1777.     --mouse show/hide area
  1778.     for k,cords in pairs(osc_param.areas["showhide"]) do
  1779.         mp.set_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "showhide")
  1780.     end
  1781.     do_enable_keybindings()
  1782.  
  1783.     --mouse input area
  1784.     local mouse_over_osc = false
  1785.  
  1786.     for _,cords in ipairs(osc_param.areas["input"]) do
  1787.         if state.osc_visible then -- activate only when OSC is actually visible
  1788.             mp.set_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "input")
  1789.         end
  1790.         if state.osc_visible ~= state.input_enabled then
  1791.             if state.osc_visible then
  1792.                 mp.enable_key_bindings("input")
  1793.             else
  1794.                 mp.disable_key_bindings("input")
  1795.             end
  1796.             state.input_enabled = state.osc_visible
  1797.         end
  1798.  
  1799.         if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then
  1800.             mouse_over_osc = true
  1801.         end
  1802.     end
  1803.  
  1804.     -- autohide
  1805.     if not (state.showtime == nil) and (user_opts.hidetimeout >= 0)
  1806.         and (state.showtime + (user_opts.hidetimeout/1000) < now)
  1807.         and (state.active_element == nil) and not (mouse_over_osc) then
  1808.  
  1809.         hide_osc()
  1810.     end
  1811.  
  1812.  
  1813.     -- actual rendering
  1814.     local ass = assdraw.ass_new()
  1815.  
  1816.     -- Messages
  1817.     render_message(ass)
  1818.  
  1819.     -- actual OSC
  1820.     if state.osc_visible then
  1821.         render_elements(ass)
  1822.     end
  1823.  
  1824.     -- submit
  1825.     mp.set_osd_ass(osc_param.playresy * aspect, osc_param.playresy, ass.text)
  1826.  
  1827.  
  1828.  
  1829.  
  1830. end
  1831.  
  1832. --
  1833. -- Eventhandling
  1834. --
  1835.  
  1836. function process_event(source, what)
  1837.  
  1838.     if what == "down" then
  1839.  
  1840.         for n = 1, #elements do
  1841.  
  1842.             if not (elements[n].eventresponder == nil) then
  1843.                 if not (elements[n].eventresponder[source .. "_up"] == nil)
  1844.                     or not (elements[n].eventresponder[source .. "_down"] == nil) then
  1845.  
  1846.                     if mouse_hit(elements[n]) then
  1847.                         state.active_element = n
  1848.                         state.active_event_source = source
  1849.                         -- fire the down event if the element has one
  1850.                         if not (elements[n].eventresponder[source .. "_" .. what] == nil) then
  1851.                             elements[n].eventresponder[source .. "_" .. what](elements[n])
  1852.                         end
  1853.                     end
  1854.                 end
  1855.  
  1856.             end
  1857.         end
  1858.  
  1859.     elseif what == "up" then
  1860.  
  1861.         if not (state.active_element == nil) then
  1862.  
  1863.             local n = state.active_element
  1864.  
  1865.             if n == 0 then
  1866.                 --click on background (does not work)
  1867.             elseif n > 0 and not (elements[n].eventresponder[source .. "_" .. what] == nil) then
  1868.  
  1869.                 if mouse_hit(elements[n]) then
  1870.                     elements[n].eventresponder[source .. "_" .. what](elements[n])
  1871.                 end
  1872.             end
  1873.  
  1874.             --reset active element
  1875.             if not (elements[n].eventresponder["reset"] == nil) then
  1876.                 elements[n].eventresponder["reset"](elements[n])
  1877.             end
  1878.  
  1879.         end
  1880.         state.active_element = nil
  1881.         state.mouse_down_counter = 0
  1882.  
  1883.     elseif source == "mouse_move" then
  1884.         local mouseX, mouseY = mp.get_mouse_pos()
  1885.         if (user_opts.minmousemove == 0) or
  1886.             (not ((state.last_mouseX == nil) or (state.last_mouseY == nil)) and
  1887.                 ((math.abs(mouseX - state.last_mouseX) >= user_opts.minmousemove)
  1888.                     or (math.abs(mouseY - state.last_mouseY) >= user_opts.minmousemove)
  1889.                 )
  1890.             ) then
  1891.             show_osc()
  1892.         end
  1893.         state.last_mouseX, state.last_mouseY = mouseX, mouseY
  1894.  
  1895.         if not (state.active_element == nil) then
  1896.  
  1897.             local n = state.active_element
  1898.  
  1899.             if not (elements[n].eventresponder == nil) then
  1900.                 if not (elements[n].eventresponder[source] == nil) then
  1901.                     elements[n].eventresponder[source](elements[n])
  1902.                 end
  1903.             end
  1904.         end
  1905.         tick()
  1906.     end
  1907. end
  1908.  
  1909. -- called by mpv on every frame
  1910. function tick()
  1911.     if (not state.enabled) then return end
  1912.  
  1913.     if (state.idle) then
  1914.  
  1915.         -- render idle message
  1916.         msg.debug("idle message")
  1917.         local icon_x, icon_y = 320 - 26, 140
  1918.  
  1919.         local ass = assdraw.ass_new()
  1920.         ass:new_event()
  1921.         ass:pos(icon_x, icon_y)
  1922.         ass:append("{\\rDefault\\an7\\c&H430142&\\1a&H00&\\bord0\\shad0\\p6}m 1605 828 b 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200 1324 200 1605 482 1605 828{\\p0}")
  1923.         ass:new_event()
  1924.         ass:pos(icon_x, icon_y)
  1925.         ass:append("{\\rDefault\\an7\\c&HDDDBDD&\\1a&H00&\\bord0\\shad0\\p6}m 1296 910 b 1296 1131 1117 1310 897 1310 676 1310 497 1131 497 910 497 689 676 511 897 511 1117 511 1296 689 1296 910{\\p0}")
  1926.         ass:new_event()
  1927.         ass:pos(icon_x, icon_y)
  1928.         ass:append("{\\rDefault\\an7\\c&H691F69&\\1a&H00&\\bord0\\shad0\\p6}m 762 1113 l 762 708 b 881 776 1000 843 1119 911 1000 978 881 1046 762 1113{\\p0}")
  1929.         ass:new_event()
  1930.         ass:pos(icon_x, icon_y)
  1931.         ass:append("{\\rDefault\\an7\\c&H682167&\\1a&H00&\\bord0\\shad0\\p6}m 925 42 b 463 42 87 418 87 880 87 1343 463 1718 925 1718 1388 1718 1763 1343 1763 880 1763 418 1388 42 925 42 m 925 42 m 977 200 b 1324 200 1605 482 1605 828 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200{\\p0}")
  1932.         ass:new_event()
  1933.         ass:pos(icon_x, icon_y)
  1934.         ass:append("{\\rDefault\\an7\\c&H753074&\\1a&H00&\\bord0\\shad0\\p6}m 977 198 b 630 198 348 480 348 828 348 1176 630 1458 977 1458 1325 1458 1607 1176 1607 828 1607 480 1325 198 977 198 m 977 198 m 977 202 b 1323 202 1604 483 1604 828 1604 1174 1323 1454 977 1454 632 1454 351 1174 351 828 351 483 632 202 977 202{\\p0}")
  1935.         ass:new_event()
  1936.         ass:pos(icon_x, icon_y)
  1937.         ass:append("{\\rDefault\\an7\\c&HE5E5E5&\\1a&H00&\\bord0\\shad0\\p6}m 895 10 b 401 10 0 410 0 905 0 1399 401 1800 895 1800 1390 1800 1790 1399 1790 905 1790 410 1390 10 895 10 m 895 10 m 925 42 b 1388 42 1763 418 1763 880 1763 1343 1388 1718 925 1718 463 1718 87 1343 87 880 87 418 463 42 925 42{\\p0}")
  1938.         ass:new_event()
  1939.         ass:pos(320, icon_y+65)
  1940.         ass:an(8)
  1941.         ass:append("Drop files to play here.")
  1942.         mp.set_osd_ass(640, 360, ass.text)
  1943.  
  1944.         if state.showhide_enabled then
  1945.             mp.disable_key_bindings("showhide")
  1946.             state.showhide_enabled = false
  1947.         end
  1948.  
  1949.  
  1950.     elseif (state.fullscreen and user_opts.showfullscreen)
  1951.         or (not state.fullscreen and user_opts.showwindowed) then
  1952.  
  1953.         -- render the OSC
  1954.         render()
  1955.     else
  1956.         -- Flush OSD
  1957.         mp.set_osd_ass(osc_param.playresy, osc_param.playresy, "")
  1958.     end
  1959. end
  1960.  
  1961. function do_enable_keybindings()
  1962.     if state.enabled then
  1963.         if not state.showhide_enabled then
  1964.             mp.enable_key_bindings("showhide", "allow-vo-dragging+allow-hide-cursor")
  1965.         end
  1966.         state.showhide_enabled = true
  1967.     end
  1968. end
  1969.  
  1970. function enable_osc(enable)
  1971.     state.enabled = enable
  1972.     if enable then
  1973.         do_enable_keybindings()
  1974.         show_osc()
  1975.     else
  1976.         hide_osc()
  1977.         if state.showhide_enabled then
  1978.             mp.disable_key_bindings("showhide")
  1979.         end
  1980.         state.showhide_enabled = false
  1981.     end
  1982. end
  1983.  
  1984. validate_user_opts()
  1985.  
  1986. mp.register_event("start-file", request_init)
  1987. mp.register_event("tracks-changed", request_init)
  1988. mp.observe_property("playlist", nil, request_init)
  1989.  
  1990. mp.register_script_message("enable-osc", function() enable_osc(true) end)
  1991. mp.register_script_message("disable-osc", function() enable_osc(false) end)
  1992.  
  1993. mp.register_script_message("osc-message", show_message)
  1994.  
  1995. mp.observe_property("fullscreen", "bool",
  1996.     function(name, val)
  1997.         state.fullscreen = val
  1998.     end
  1999. )
  2000. mp.observe_property("idle", "bool",
  2001.     function(name, val)
  2002.         state.idle = val
  2003.         tick()
  2004.     end
  2005. )
  2006. mp.observe_property("pause", "bool", pause_state)
  2007. mp.observe_property("cache-idle", "bool", cache_state)
  2008. mp.observe_property("vo-configured", "bool", function(name, val)
  2009.     if val then
  2010.         mp.register_event("tick", tick)
  2011.     else
  2012.         mp.unregister_event(tick)
  2013.     end
  2014. end)
  2015.  
  2016. -- mouse show/hide bindings
  2017. mp.set_key_bindings({
  2018.     {"mouse_move",              function(e) process_event("mouse_move", nil) end},
  2019.     {"mouse_leave",             mouse_leave},
  2020. }, "showhide", "force")
  2021. do_enable_keybindings()
  2022.  
  2023. --mouse input bindings
  2024. mp.set_key_bindings({
  2025.     {"mouse_btn0",              function(e) process_event("mouse_btn0", "up") end,
  2026.                                 function(e) process_event("mouse_btn0", "down")  end},
  2027.     {"shift+mouse_btn0",        function(e) process_event("shift+mouse_btn0", "up") end,
  2028.                                 function(e) process_event("shift+mouse_btn0", "down")  end},
  2029.     {"mouse_btn2",              function(e) process_event("mouse_btn2", "up") end,
  2030.                                 function(e) process_event("mouse_btn2", "down")  end},
  2031.     {"mouse_btn0_dbl",          "ignore"},
  2032.     {"shift+mouse_btn0_dbl",    "ignore"},
  2033.     {"mouse_btn2_dbl",          "ignore"},
  2034.     {"del",                     function() enable_osc(false) end}
  2035. }, "input", "force")
  2036. mp.enable_key_bindings("input")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement