update
This commit is contained in:
@@ -36,4 +36,4 @@ function BufferingIndicator:render()
|
||||
return ass
|
||||
end
|
||||
|
||||
return BufferingIndicator
|
||||
return BufferingIndicator
|
||||
@@ -40,7 +40,7 @@ function Button:render()
|
||||
|
||||
local ass = assdraw.ass_new()
|
||||
local is_clickable = self.is_clickable and self.on_click ~= nil
|
||||
local is_hover = self.proximity_raw == 0
|
||||
local is_hover = self.proximity_raw <= 0
|
||||
local foreground = self.active and self.background or self.foreground
|
||||
local background = self.active and self.foreground or self.background
|
||||
local background_opacity = self.active and 1 or config.opacity.controls
|
||||
@@ -97,4 +97,4 @@ function Button:render()
|
||||
return ass
|
||||
end
|
||||
|
||||
return Button
|
||||
return Button
|
||||
@@ -37,17 +37,17 @@ function Controls:init_options()
|
||||
-- Serialize control elements
|
||||
local shorthands = {
|
||||
['play-pause'] = 'cycle:pause:pause:no/yes=play_arrow?' .. t('Play/Pause'),
|
||||
menu = 'command:menu_book:script-binding uosc/menu-blurred?' .. t('Menu'),
|
||||
subtitles = 'command:closed_caption:script-binding uosc/subtitles#sub>1?' .. t('Subtitles'),
|
||||
menu = 'command:menu:script-binding uosc/menu-blurred?' .. t('Menu'),
|
||||
subtitles = 'command:subtitles:script-binding uosc/subtitles#sub>0?' .. t('Subtitles'),
|
||||
audio = 'command:graphic_eq:script-binding uosc/audio#audio>1?' .. t('Audio'),
|
||||
['audio-device'] = 'command:speaker:script-binding uosc/audio-device?' .. t('Audio device'),
|
||||
video = 'command:smart_display:script-binding uosc/video#video>1?' .. t('Video'),
|
||||
playlist = 'command:list_alt:script-binding uosc/playlist#playlist>1?' .. t('Playlist'),
|
||||
chapters = 'command:library_books:script-binding uosc/chapters#chapters>1?' .. t('Chapters'),
|
||||
['editions'] = 'command:movie_filter:script-binding uosc/editions#editions>1?' .. t('Editions'),
|
||||
video = 'command:theaters:script-binding uosc/video#video>1?' .. t('Video'),
|
||||
playlist = 'command:list_alt:script-binding uosc/playlist?' .. t('Playlist'),
|
||||
chapters = 'command:bookmark:script-binding uosc/chapters#chapters>0?' .. t('Chapters'),
|
||||
['editions'] = 'command:bookmarks:script-binding uosc/editions#editions>1?' .. t('Editions'),
|
||||
['stream-quality'] = 'command:high_quality:script-binding uosc/stream-quality?' .. t('Stream quality'),
|
||||
['open-file'] = 'command:folder:script-binding uosc/open-file?' .. t('Open file'),
|
||||
['items'] = 'command:list_alt:script-binding uosc/items#playlist>1?' .. t('Playlist/Files'),
|
||||
['open-file'] = 'command:file_open:script-binding uosc/open-file?' .. t('Open file'),
|
||||
['items'] = 'command:list_alt:script-binding uosc/items?' .. t('Playlist/Files'),
|
||||
prev = 'command:arrow_back_ios:script-binding uosc/prev?' .. t('Previous'),
|
||||
next = 'command:arrow_forward_ios:script-binding uosc/next?' .. t('Next'),
|
||||
first = 'command:first_page:script-binding uosc/first?' .. t('First'),
|
||||
@@ -271,9 +271,6 @@ function Controls:register_badge_updater(badge, element)
|
||||
for _, track in ipairs(value) do if track.type == prop then count = count + 1 end end
|
||||
return count
|
||||
end
|
||||
elseif prop == 'playlist' then
|
||||
observable_name = 'playlist-count'
|
||||
serializer = function(count) return count end
|
||||
else
|
||||
local parts = split(prop, '@')
|
||||
-- Support both new `prop@owner` and old `@prop` syntaxes
|
||||
@@ -428,4 +425,4 @@ function Controls:on_options()
|
||||
self:init_options()
|
||||
end
|
||||
|
||||
return Controls
|
||||
return Controls
|
||||
@@ -32,4 +32,4 @@ function Curtain:render()
|
||||
return ass
|
||||
end
|
||||
|
||||
return Curtain
|
||||
return Curtain
|
||||
@@ -83,4 +83,4 @@ function CycleButton:init(id, props)
|
||||
end
|
||||
end
|
||||
|
||||
return CycleButton
|
||||
return CycleButton
|
||||
@@ -262,4 +262,4 @@ function Element:create_action(fn)
|
||||
end
|
||||
end
|
||||
|
||||
return Element
|
||||
return Element
|
||||
@@ -48,14 +48,14 @@ function Elements:update_proximities()
|
||||
element:update_proximity()
|
||||
end
|
||||
|
||||
if element.proximity_raw == 0 then
|
||||
if element.proximity_raw <= 0 then
|
||||
-- Mouse entered element area
|
||||
if previous_proximity_raw ~= 0 then
|
||||
if previous_proximity_raw > 0 then
|
||||
mouse_enter_elements[#mouse_enter_elements + 1] = element
|
||||
end
|
||||
else
|
||||
-- Mouse left element area
|
||||
if previous_proximity_raw == 0 then
|
||||
if previous_proximity_raw <= 0 then
|
||||
mouse_leave_elements[#mouse_leave_elements + 1] = element
|
||||
end
|
||||
end
|
||||
@@ -122,7 +122,7 @@ function Elements:proximity_trigger(name, ...)
|
||||
for i = #self._all, 1, -1 do
|
||||
local element = self._all[i]
|
||||
if element.enabled then
|
||||
if element.proximity_raw == 0 then
|
||||
if element.proximity_raw <= 0 then
|
||||
if element:trigger(name, ...) == 'stop_propagation' then break end
|
||||
end
|
||||
if element:trigger('global_' .. name, ...) == 'stop_propagation' then break end
|
||||
@@ -149,4 +149,4 @@ end
|
||||
function Elements:has(id) return self[id] ~= nil end
|
||||
function Elements:ipairs() return ipairs(self._all) end
|
||||
|
||||
return Elements
|
||||
return Elements
|
||||
@@ -33,4 +33,4 @@ function ManagedButton:update(data)
|
||||
end
|
||||
end
|
||||
|
||||
return ManagedButton
|
||||
return ManagedButton
|
||||
+110
-118
@@ -309,7 +309,7 @@ function Menu:update_content_dimensions()
|
||||
|
||||
for _, menu in ipairs(self.all) do
|
||||
title_opts.bold, title_opts.italic = true, false
|
||||
local max_width = text_width(menu.title, title_opts) + 2 * self.padding + 2 * self.item_padding
|
||||
local max_width = text_width(menu.title, title_opts) + 2 * self.item_padding
|
||||
|
||||
-- Estimate width of a widest item
|
||||
for _, item in ipairs(menu.items) do
|
||||
@@ -323,7 +323,7 @@ function Menu:update_content_dimensions()
|
||||
if estimated_width > max_width then max_width = estimated_width end
|
||||
end
|
||||
|
||||
menu.max_width = max_width + 2 * self.padding
|
||||
menu.max_width = max_width
|
||||
end
|
||||
|
||||
self:update_dimensions()
|
||||
@@ -336,20 +336,21 @@ function Menu:update_dimensions()
|
||||
-- and dumb titles with no search inputs. It could use a refactor.
|
||||
local margin = round(self.item_height / 2)
|
||||
local external_buttons_reserve = display.width / self.item_height > 14 and self.scroll_step * 6 - margin * 2 or 0
|
||||
local width_available = display.width - margin * 2 - external_buttons_reserve
|
||||
local height_available = display.height - margin * 2
|
||||
local width_available = display.width - margin * 2 - self.padding * 2 - external_buttons_reserve
|
||||
local height_available = display.height - margin * 2 - self.padding * 2
|
||||
local min_width = math.min(self.min_width, width_available)
|
||||
|
||||
for _, menu in ipairs(self.all) do
|
||||
local width = math.max(menu.search and menu.search.max_width or 0, menu.max_width)
|
||||
menu.width = round(clamp(min_width, width, width_available))
|
||||
local title_height = (menu.is_root and menu.title or menu.search) and self.scroll_step + self.padding or 0
|
||||
local title_height = (menu.is_root and menu.title or menu.search) and
|
||||
self.scroll_step + self.separator_size + 1 or 0
|
||||
local footnote_height = self.font_size * 1.5
|
||||
local max_height = height_available - title_height - footnote_height
|
||||
local content_height = self.scroll_step * #menu.items
|
||||
menu.height = math.min(content_height - self.item_spacing, max_height)
|
||||
menu.top = clamp(
|
||||
title_height + margin,
|
||||
title_height + margin + self.padding,
|
||||
menu.search and math.min(menu.search.min_top, menu.search.source.top) or height_available,
|
||||
round((height_available - menu.height + title_height) / 2)
|
||||
)
|
||||
@@ -364,10 +365,13 @@ function Menu:update_dimensions()
|
||||
self:update_coordinates()
|
||||
end
|
||||
|
||||
-- Updates element coordinates to match currently open (sub)menu.
|
||||
-- Updates element coordinates to match padding box of currently open (sub)menu.
|
||||
function Menu:update_coordinates()
|
||||
local ax = round((display.width - self.current.width) / 2) + self.offset_x
|
||||
self:set_coordinates(ax, self.current.top, ax + self.current.width, self.current.top + self.current.height)
|
||||
local ax = round((display.width - self.current.width) / 2 - self.padding) + self.offset_x
|
||||
self:set_coordinates(
|
||||
ax, self.current.top - self.padding,
|
||||
ax + self.current.width + self.padding * 2, self.current.top + self.current.height + self.padding
|
||||
)
|
||||
end
|
||||
|
||||
function Menu:reset_navigation()
|
||||
@@ -686,7 +690,7 @@ function Menu:on_prop_fullormaxed() self:update_content_dimensions() end
|
||||
function Menu:on_options() self:update_content_dimensions() end
|
||||
|
||||
function Menu:handle_cursor_down()
|
||||
if self.proximity_raw == 0 then
|
||||
if self.proximity_raw <= 0 then
|
||||
self.drag_last_y = cursor.y
|
||||
self.current.fling = nil
|
||||
else
|
||||
@@ -696,7 +700,7 @@ end
|
||||
|
||||
---@param shortcut? Shortcut
|
||||
function Menu:handle_cursor_up(shortcut)
|
||||
if self.proximity_raw == 0 and self.drag_last_y and not self.is_dragging then
|
||||
if self.proximity_raw <= -self.padding and self.drag_last_y and not self.is_dragging then
|
||||
self:activate_selected_item(shortcut, true)
|
||||
end
|
||||
if self.is_dragging then
|
||||
@@ -893,14 +897,14 @@ function search_items(items, query, recursive, prefix)
|
||||
if ligature_conv_title:find(query, 1, true) then
|
||||
match = true
|
||||
score = 1000
|
||||
local pos = get_roman_match_positions(title, query, "ligature", ligature_roman)
|
||||
local pos = get_roman_match_positions(title, query, 'ligature', ligature_roman)
|
||||
if pos then
|
||||
ass_safe_title = highlight_match(item.title, pos, font_color, bold)
|
||||
end
|
||||
elseif initials_conv_title:find(query, 1, true) then
|
||||
match = true
|
||||
score = 900
|
||||
local pos = get_roman_match_positions(title, query, "initial", initials_roman)
|
||||
local pos = get_roman_match_positions(title, query, 'initial', initials_roman)
|
||||
if pos then
|
||||
ass_safe_title = highlight_match(item.title, pos, font_color, bold)
|
||||
end
|
||||
@@ -1371,7 +1375,6 @@ function Menu:render()
|
||||
cursor:zone('wheel_up', self, function() self:handle_wheel_up() end)
|
||||
|
||||
local ass = assdraw.ass_new()
|
||||
local spacing = self.item_padding
|
||||
local icon_size = self.font_size
|
||||
|
||||
---@param menu MenuStack
|
||||
@@ -1380,37 +1383,43 @@ function Menu:render()
|
||||
local function draw_menu(menu, x, pos)
|
||||
local is_current, is_parent, is_submenu = pos == 0, pos < 0, pos > 0
|
||||
local menu_opacity = (pos == 0 and 1 or config.opacity.submenu ^ math.abs(pos)) * self.opacity
|
||||
local ax, ay, bx, by = x, menu.top, x + menu.width, menu.top + menu.height
|
||||
-- Scrollable content area coordinates
|
||||
local content_rect = {
|
||||
ax = x + self.padding,
|
||||
ay = menu.top,
|
||||
bx = x + self.padding + menu.width,
|
||||
by = menu.top + menu.height,
|
||||
}
|
||||
-- local ax, ay, bx, by = x + self.padding, menu.top, x + menu.width + self.padding, menu.top + menu.height
|
||||
local draw_title = menu.is_root and menu.title or menu.search
|
||||
local scroll_clip = '\\clip(0,' .. ay .. ',' .. display.width .. ',' .. by .. ')'
|
||||
local scroll_clip = '\\clip(0,' .. content_rect.ay .. ',' .. display.width .. ',' .. content_rect.by .. ')'
|
||||
local start_index = math.floor(menu.scroll_y / self.scroll_step) + 1
|
||||
local end_index = math.ceil((menu.scroll_y + menu.height) / self.scroll_step)
|
||||
local menu_rect = {
|
||||
ax = ax,
|
||||
ay = ay - (draw_title and self.scroll_step + self.padding or 0) - self.padding,
|
||||
bx = bx,
|
||||
by = by + self.padding,
|
||||
local bg_rect = {
|
||||
ax = x,
|
||||
ay = content_rect.ay - (draw_title and self.scroll_step or 0) - self.padding,
|
||||
bx = content_rect.bx + self.padding,
|
||||
by = content_rect.by + self.padding,
|
||||
}
|
||||
local blur_selected_index = self.mouse_nav and is_current
|
||||
local blur_action_index = self.mouse_nav and menu.action_index ~= nil
|
||||
|
||||
-- Background
|
||||
ass:rect(menu_rect.ax, menu_rect.ay, menu_rect.bx, menu_rect.by, {
|
||||
ass:rect(bg_rect.ax, bg_rect.ay, bg_rect.bx, bg_rect.by, {
|
||||
color = bg,
|
||||
opacity = menu_opacity * config.opacity.menu,
|
||||
radius = state.radius > 0 and state.radius + self.padding or 0,
|
||||
radius = state.radius > 0 and math.min(state.radius + self.padding, state.radius * 3) or 0,
|
||||
})
|
||||
|
||||
if is_parent then
|
||||
cursor:zone('primary_down', menu_rect, self:create_action(function() self:slide_in_menu(menu.id, x) end))
|
||||
cursor:zone('primary_down', bg_rect, self:create_action(function() self:slide_in_menu(menu.id, x) end))
|
||||
end
|
||||
|
||||
-- Scrollbar
|
||||
if menu.scroll_height > 0 then
|
||||
local groove_height = menu.height - 2
|
||||
local thumb_height = math.max((menu.height / (menu.scroll_height + menu.height)) * groove_height, 40)
|
||||
local thumb_y = ay + 1 + ((menu.scroll_y / menu.scroll_height) * (groove_height - thumb_height))
|
||||
local sax = bx - round(self.scrollbar_size / 2)
|
||||
local thumb_y = content_rect.ay + 1 + ((menu.scroll_y / menu.scroll_height) * (groove_height - thumb_height))
|
||||
local sax = content_rect.bx - round(self.scrollbar_size / 2)
|
||||
local sbx = sax + self.scrollbar_size
|
||||
ass:rect(sax, thumb_y, sbx, thumb_y + thumb_height, {color = fg, opacity = menu_opacity * 0.8})
|
||||
end
|
||||
@@ -1419,7 +1428,7 @@ function Menu:render()
|
||||
local submenu_rect, current_item = nil, is_current and menu.selected_index and menu.items[menu.selected_index]
|
||||
local submenu_is_hovered = false
|
||||
if current_item and current_item.items then
|
||||
submenu_rect = draw_menu(current_item --[[@as MenuStack]], menu_rect.bx + self.gap, 1)
|
||||
submenu_rect = draw_menu(current_item --[[@as MenuStack]], bg_rect.bx + self.gap, 1)
|
||||
cursor:zone('primary_down', submenu_rect, self:create_action(function(shortcut)
|
||||
self:activate_selected_item(shortcut, true)
|
||||
end))
|
||||
@@ -1432,21 +1441,32 @@ function Menu:render()
|
||||
|
||||
if not item then break end
|
||||
|
||||
local item_ax = menu_rect.ax + self.padding
|
||||
local item_bx = menu_rect.bx - self.padding
|
||||
local item_ay = ay - menu.scroll_y + self.scroll_step * (index - 1)
|
||||
local item_ay = content_rect.ay - menu.scroll_y + self.scroll_step * (index - 1)
|
||||
local item_by = item_ay + self.item_height
|
||||
local item_center_y = item_ay + (self.item_height / 2)
|
||||
local item_clip = (item_ay < ay or item_by > by) and scroll_clip or nil
|
||||
local content_ax, content_bx = ax + self.padding + spacing, bx - self.padding - spacing
|
||||
local item_clip = (item_ay < content_rect.ay or item_by > content_rect.by) and scroll_clip or nil
|
||||
local content_ax, content_bx = content_rect.ax + self.item_padding,
|
||||
content_rect.bx - self.item_padding
|
||||
local is_selected = menu.selected_index == index
|
||||
local item_rect_hitbox = {
|
||||
ax = item_ax,
|
||||
ay = math.max(item_ay, menu_rect.ay),
|
||||
bx = menu_rect.bx + (item.items and self.gap or -self.padding), -- to bridge the gap with cursor
|
||||
by = math.min(item_ay + self.scroll_step, menu_rect.by),
|
||||
ax = content_rect.ax,
|
||||
ay = math.max(item_ay, bg_rect.ay),
|
||||
bx = bg_rect.bx + (item.items and self.gap or -self.padding), -- to bridge the submenu gap with cursor
|
||||
by = math.min(item_ay + self.scroll_step, bg_rect.by),
|
||||
}
|
||||
|
||||
-- Select hovered item
|
||||
if is_current and self.mouse_nav and item.selectable ~= false
|
||||
-- Do not select items if cursor is moving towards a submenu
|
||||
and (not submenu_rect or not cursor:direction_to_rectangle_distance(submenu_rect))
|
||||
and (submenu_is_hovered or get_point_to_rectangle_proximity(cursor, item_rect_hitbox) <= 0) then
|
||||
menu.selected_index = index
|
||||
if not is_selected then
|
||||
is_selected = true
|
||||
request_render()
|
||||
end
|
||||
end
|
||||
|
||||
local has_background = is_selected or item.active
|
||||
local next_item = menu.items[index + 1]
|
||||
local next_is_active = next_item and next_item.active
|
||||
@@ -1458,22 +1478,23 @@ function Menu:render()
|
||||
if action then selected_action = action end
|
||||
|
||||
-- Separator
|
||||
if item_by < by and ((not has_background and not next_has_background) or item.separator) then
|
||||
local separator_ay, separator_by = item_by, item_by + self.separator_size
|
||||
if item_by < content_rect.by and ((not has_background and not next_has_background) or item.separator) then
|
||||
local ay, by = item_by, item_by + self.separator_size
|
||||
if has_background then
|
||||
separator_ay, separator_by = separator_ay + self.separator_size, separator_by + self.separator_size
|
||||
ay, by = ay + self.separator_size, by + self.separator_size
|
||||
elseif next_has_background then
|
||||
separator_ay, separator_by = separator_ay - self.separator_size, separator_by - self.separator_size
|
||||
ay, by = ay - self.separator_size, by - self.separator_size
|
||||
end
|
||||
ass:rect(ax + spacing, separator_ay, bx - spacing, separator_by, {
|
||||
color = fg, opacity = menu_opacity * (item.separator and 0.13 or 0.04),
|
||||
})
|
||||
ass:rect(
|
||||
content_rect.ax + self.item_padding, ay, content_rect.bx - self.item_padding, by,
|
||||
{color = fg, opacity = menu_opacity * (item.separator and 0.13 or 0.04)}
|
||||
)
|
||||
end
|
||||
|
||||
-- Background
|
||||
local highlight_opacity = 0 + (item.active and 0.8 or 0) + (is_selected and 0.15 or 0)
|
||||
if highlight_opacity > 0 then
|
||||
ass:rect(ax + self.padding, item_ay, bx - self.padding, item_by, {
|
||||
ass:rect(content_rect.ax, item_ay, content_rect.bx, item_by, {
|
||||
radius = state.radius,
|
||||
color = fg,
|
||||
opacity = highlight_opacity * menu_opacity,
|
||||
@@ -1495,9 +1516,10 @@ function Menu:render()
|
||||
actions_rect = {
|
||||
ay = item_ay + margin,
|
||||
by = item_by - margin,
|
||||
is_outside = place == 'outside' and display.width - menu_rect.bx + margin * 2 > rect_width,
|
||||
is_outside = place == 'outside' and display.width - bg_rect.bx + margin * 2 > rect_width,
|
||||
}
|
||||
actions_rect.bx = actions_rect.is_outside and menu_rect.bx + margin + rect_width or item_bx - margin
|
||||
actions_rect.bx = actions_rect.is_outside and bg_rect.bx + margin + rect_width or
|
||||
content_rect.bx - margin
|
||||
actions_rect.ax = actions_rect.bx
|
||||
|
||||
for i = 1, #actions, 1 do
|
||||
@@ -1532,7 +1554,7 @@ function Menu:render()
|
||||
rect.ay, rect.by, rect.bx = item_ay, item_ay + self.scroll_step, rect.bx + margin
|
||||
|
||||
-- Select action on cursor hover
|
||||
if self.mouse_nav and get_point_to_rectangle_proximity(cursor, rect) == 0 then
|
||||
if self.mouse_nav and get_point_to_rectangle_proximity(cursor, rect) <= 0 then
|
||||
cursor:zone('primary_down', rect, self:create_action(function(shortcut)
|
||||
self:activate_selected_item(shortcut, true)
|
||||
end))
|
||||
@@ -1553,17 +1575,18 @@ function Menu:render()
|
||||
if is_selected and not selected_action then
|
||||
local size = round(2 * state.scale)
|
||||
local v_padding = math.min(state.radius, math.ceil(self.item_height / 3))
|
||||
ass:rect(ax + self.padding - size - 1, item_ay + v_padding, ax + self.padding - 1,
|
||||
item_by - v_padding, {
|
||||
radius = 1 * state.scale, color = fg, opacity = menu_opacity, clip = item_clip,
|
||||
})
|
||||
ass:rect(
|
||||
content_rect.ax - size - 1, item_ay + v_padding,
|
||||
content_rect.ax - 1, item_by - v_padding,
|
||||
{radius = 1 * state.scale, color = fg, opacity = menu_opacity, clip = item_clip}
|
||||
)
|
||||
end
|
||||
|
||||
-- Icon
|
||||
if item.icon then
|
||||
if not actions_rect or actions_rect.is_outside then
|
||||
local x = (not item.title and not item.hint and item.align == 'center')
|
||||
and menu_rect.ax + (menu_rect.bx - menu_rect.ax) / 2
|
||||
and bg_rect.ax + (bg_rect.bx - bg_rect.ax) / 2
|
||||
or content_bx - (icon_size / 2)
|
||||
if item.icon == 'spinner' then
|
||||
ass:spinner(x, item_center_y, icon_size * 1.5, {color = font_color, opacity = menu_opacity * 0.8})
|
||||
@@ -1573,7 +1596,7 @@ function Menu:render()
|
||||
})
|
||||
end
|
||||
end
|
||||
content_bx = content_bx - icon_size - spacing
|
||||
content_bx = content_bx - icon_size - self.item_padding
|
||||
title_clip_bx = math.min(content_bx, title_clip_bx)
|
||||
end
|
||||
|
||||
@@ -1581,7 +1604,7 @@ function Menu:render()
|
||||
if item.hint_width > 0 then
|
||||
-- controls title & hint clipping proportional to the ratio of their widths
|
||||
-- both title and hint get at least 50% of the width, unless they are smaller then that
|
||||
local width = content_bx - content_ax - spacing
|
||||
local width = content_bx - content_ax - self.item_padding
|
||||
local title_min = math.min(item.title_width, width * 0.5)
|
||||
local hint_min = math.min(item.hint_width, width * 0.5)
|
||||
local title_ratio = item.title_width / (item.title_width + item.hint_width)
|
||||
@@ -1594,8 +1617,9 @@ function Menu:render()
|
||||
-- Hint
|
||||
if item.hint then
|
||||
item.ass_safe_hint = item.ass_safe_hint or ass_escape(item.hint)
|
||||
local clip = '\\clip(' .. title_clip_bx + spacing .. ',' ..
|
||||
math.max(item_ay, ay) .. ',' .. hint_clip_bx .. ',' .. math.min(item_by, by) .. ')'
|
||||
local clip = '\\clip(' .. title_clip_bx + self.item_padding .. ','
|
||||
.. math.max(item_ay, content_rect.ay) .. ',' .. hint_clip_bx .. ','
|
||||
.. math.min(item_by, content_rect.by) .. ')'
|
||||
ass:txt(content_bx, item_center_y, 6, item.ass_safe_hint, {
|
||||
size = self.font_size_hint,
|
||||
color = font_color,
|
||||
@@ -1608,8 +1632,8 @@ function Menu:render()
|
||||
-- Title
|
||||
if item.title then
|
||||
item.ass_safe_title = item.ass_safe_title or ass_escape(item.title)
|
||||
local clip = '\\clip(' .. ax .. ',' .. math.max(item_ay, ay) .. ','
|
||||
.. title_clip_bx .. ',' .. math.min(item_by, by) .. ')'
|
||||
local clip = '\\clip(' .. content_rect.ax .. ',' .. math.max(item_ay, content_rect.ay) .. ','
|
||||
.. title_clip_bx .. ',' .. math.min(item_by, content_rect.by) .. ')'
|
||||
local title_x, align = content_ax, 4
|
||||
if item.align == 'right' then
|
||||
title_x, align = title_clip_bx, 6
|
||||
@@ -1626,29 +1650,12 @@ function Menu:render()
|
||||
clip = clip,
|
||||
})
|
||||
end
|
||||
|
||||
-- Select hovered item
|
||||
if is_current and self.mouse_nav and item.selectable ~= false then
|
||||
if submenu_rect and cursor:direction_to_rectangle_distance(submenu_rect)
|
||||
or actions_rect and actions_rect.is_outside and cursor:direction_to_rectangle_distance(actions_rect) then
|
||||
blur_selected_index = false
|
||||
else
|
||||
if submenu_is_hovered or get_point_to_rectangle_proximity(cursor, item_rect_hitbox) == 0 then
|
||||
blur_selected_index = false
|
||||
menu.selected_index = index
|
||||
if not is_selected then
|
||||
is_selected = true
|
||||
request_render()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Footnote / Selected action label
|
||||
if is_current and (menu.footnote or selected_action) then
|
||||
local height_half = self.font_size
|
||||
local icon_x, icon_y = menu_rect.ax + self.padding + self.font_size / 2, menu_rect.by + height_half
|
||||
local icon_x, icon_y = content_rect.ax + self.font_size / 2, bg_rect.by + height_half
|
||||
local is_icon_hovered = false
|
||||
local icon_hitbox = {
|
||||
ax = icon_x - height_half,
|
||||
@@ -1656,14 +1663,14 @@ function Menu:render()
|
||||
bx = icon_x + height_half,
|
||||
by = icon_y + height_half,
|
||||
}
|
||||
is_icon_hovered = get_point_to_rectangle_proximity(cursor, icon_hitbox) == 0
|
||||
is_icon_hovered = get_point_to_rectangle_proximity(cursor, icon_hitbox) <= 0
|
||||
local text = selected_action and selected_action.label or is_icon_hovered and menu.footnote
|
||||
local opacity = (is_icon_hovered and 1 or 0.5) * menu_opacity
|
||||
ass:icon(icon_x, icon_y, self.font_size, is_icon_hovered and 'help' or 'help_outline', {
|
||||
color = fg, border = state.scale, border_color = bg, opacity = opacity,
|
||||
})
|
||||
if text then
|
||||
ass:txt(icon_x + self.font_size * 0.75, icon_y, 4, text, {
|
||||
ass:txt(icon_x + self.font_size * 0.75, icon_y - self.font_size * 0.5, 7, ass_escape(text), {
|
||||
size = self.font_size,
|
||||
color = fg,
|
||||
border = state.scale,
|
||||
@@ -1676,43 +1683,24 @@ function Menu:render()
|
||||
|
||||
-- Menu title
|
||||
if draw_title then
|
||||
local title_height = self.item_height + self.padding - 3
|
||||
local requires_submit = menu.search_debounce == 'submit'
|
||||
local rect = {
|
||||
ax = round(ax + spacing / 2 + self.padding),
|
||||
ay = ay - self.scroll_step - self.padding * 2,
|
||||
bx = round(bx - spacing / 2 - self.padding),
|
||||
by = math.min(by, ay - self.padding),
|
||||
ax = content_rect.ax,
|
||||
ay = content_rect.ay - self.scroll_step - self.separator_size - 1,
|
||||
bx = content_rect.bx,
|
||||
by = content_rect.ay - self.separator_size - 1,
|
||||
}
|
||||
-- centers
|
||||
-- Centers
|
||||
rect.cx, rect.cy = round(rect.ax + (rect.bx - rect.ax) / 2), round(rect.ay + (rect.by - rect.ay) / 2)
|
||||
|
||||
if menu.title and not menu.ass_safe_title then
|
||||
menu.ass_safe_title = ass_escape(menu.title)
|
||||
end
|
||||
|
||||
-- Background
|
||||
if menu.search then
|
||||
ass:rect(ax + 3, rect.ay + 3, bx - 3, rect.ay + title_height - 1, {
|
||||
color = fg .. '\\1a&HFF', opacity = menu_opacity * 0.1,
|
||||
radius = state.radius > 0 and state.radius + self.padding or 0,
|
||||
border = 1, border_color = fg, border_opacity = menu_opacity * 0.8
|
||||
})
|
||||
ass:texture(ax + 3, rect.ay + 3, bx - 3, rect.ay + title_height - 1, 'n', {
|
||||
size = 80, color = bg, opacity = menu_opacity * 0.1, anchor_x = ax + 2, anchor_y = rect.ay + 2,
|
||||
})
|
||||
else
|
||||
ass:rect(ax + 2, rect.ay + 2, bx - 2, rect.ay + title_height, {
|
||||
color = fg, opacity = menu_opacity * 0.8,
|
||||
radius = state.radius > 0 and state.radius + self.padding or 0,
|
||||
})
|
||||
ass:texture(ax + 2, rect.ay + 2, bx - 2, rect.ay + title_height, 'n', {
|
||||
size = 80, color = bg, opacity = menu_opacity * 0.1,
|
||||
})
|
||||
end
|
||||
|
||||
-- Bottom border
|
||||
ass:rect(ax, rect.by - self.separator_size, bx, rect.by, {color = fg, opacity = menu_opacity * 0.2})
|
||||
-- Separator
|
||||
ass:rect(
|
||||
rect.ax, rect.by, rect.bx, rect.by + self.separator_size, {color = fg, opacity = menu_opacity * 0.2}
|
||||
)
|
||||
|
||||
-- Blur selection (also activates search input) when user clicks title
|
||||
if is_current then
|
||||
@@ -1725,11 +1713,16 @@ function Menu:render()
|
||||
if menu.search then
|
||||
-- Icon
|
||||
local icon_size, icon_opacity = self.font_size * 1.3, menu_opacity * (requires_submit and 0.5 or 1)
|
||||
local icon_rect = {ax = rect.ax, ay = rect.ay, bx = ax + icon_size + spacing * 1.5, by = rect.by}
|
||||
local icon_rect = {
|
||||
ax = rect.ax,
|
||||
ay = rect.ay,
|
||||
bx = content_rect.ax + icon_size + self.item_padding * 1.5,
|
||||
by = rect.by,
|
||||
}
|
||||
|
||||
if is_current and requires_submit then
|
||||
cursor:zone('primary_down', icon_rect, function() self:search_submit() end)
|
||||
if get_point_to_rectangle_proximity(cursor, icon_rect) == 0 then
|
||||
if get_point_to_rectangle_proximity(cursor, icon_rect) <= 0 then
|
||||
icon_opacity = menu_opacity
|
||||
end
|
||||
end
|
||||
@@ -1778,7 +1771,10 @@ function Menu:render()
|
||||
-- (input is selected when `selected_index` is `nil`)
|
||||
if menu.search_debounce == 'submit' and not menu.selected_index then
|
||||
local size_half = round(1 * state.scale)
|
||||
ass:rect(ax, rect.by - size_half, bx, rect.by + size_half, {color = fg, opacity = menu_opacity})
|
||||
ass:rect(
|
||||
content_rect.ax, rect.by - size_half, content_rect.bx, rect.by + size_half,
|
||||
{color = fg, opacity = menu_opacity}
|
||||
)
|
||||
end
|
||||
local input_is_blurred = menu.search_debounce == 'submit' and menu.selected_index
|
||||
|
||||
@@ -1793,7 +1789,7 @@ function Menu:render()
|
||||
ass:txt(rect.cx, rect.cy, 5, menu.ass_safe_title, {
|
||||
size = self.font_size,
|
||||
bold = true,
|
||||
color = bg,
|
||||
color = bgt,
|
||||
wrap = 2,
|
||||
opacity = menu_opacity,
|
||||
clip = '\\clip(' .. rect.ax .. ',' .. rect.ay .. ',' .. rect.bx .. ',' .. rect.by .. ')',
|
||||
@@ -1801,16 +1797,12 @@ function Menu:render()
|
||||
end
|
||||
end
|
||||
|
||||
-- We are in mouse nav and cursor isn't hovering any item
|
||||
if blur_selected_index then
|
||||
menu.selected_index = nil
|
||||
end
|
||||
if blur_action_index then
|
||||
menu.action_index = nil
|
||||
request_render()
|
||||
end
|
||||
|
||||
return menu_rect
|
||||
return bg_rect
|
||||
end
|
||||
|
||||
-- Active menu
|
||||
@@ -1821,7 +1813,7 @@ function Menu:render()
|
||||
local parent_offset_x, parent_horizontal_index = self.ax, -1
|
||||
|
||||
while parent_menu do
|
||||
parent_offset_x = parent_offset_x - parent_menu.width - self.gap
|
||||
parent_offset_x = parent_offset_x - parent_menu.width - self.padding * 2 - self.gap
|
||||
draw_menu(parent_menu, parent_offset_x, parent_horizontal_index)
|
||||
parent_horizontal_index = parent_horizontal_index - 1
|
||||
parent_menu = parent_menu.parent_menu
|
||||
@@ -1830,4 +1822,4 @@ function Menu:render()
|
||||
return ass
|
||||
end
|
||||
|
||||
return Menu
|
||||
return Menu
|
||||
@@ -14,7 +14,7 @@ function PauseIndicator:init()
|
||||
end
|
||||
|
||||
function PauseIndicator:init_options()
|
||||
self.base_icon_opacity = options.pause_indicator == 'flash' and 1 or 0.8
|
||||
self.base_icon_opacity = config.opacity.pause_indicator or (options.pause_indicator == 'flash' and 1) or 0.8
|
||||
self.type = options.pause_indicator
|
||||
self:on_prop_pause()
|
||||
end
|
||||
@@ -80,4 +80,4 @@ function PauseIndicator:render()
|
||||
return ass
|
||||
end
|
||||
|
||||
return PauseIndicator
|
||||
return PauseIndicator
|
||||
@@ -192,4 +192,4 @@ function Speed:render()
|
||||
return ass
|
||||
end
|
||||
|
||||
return Speed
|
||||
return Speed
|
||||
@@ -18,12 +18,20 @@ function Timeline:init()
|
||||
self.progress_line_width = 0
|
||||
self.is_hovered = false
|
||||
self.has_thumbnail = false
|
||||
self.heatmap = nil
|
||||
|
||||
self:decide_progress_size()
|
||||
self:update_dimensions()
|
||||
|
||||
-- Release any dragging when file gets unloaded
|
||||
self:register_mp_event('end-file', function() self.pressed = false end)
|
||||
-- Load Youtube heatmap data if available
|
||||
self:register_mp_event('file-loaded', function()
|
||||
self.heatmap = load_youtube_heatmap()
|
||||
end)
|
||||
-- Release any dragging and clear heatmap when file gets unloaded
|
||||
self:register_mp_event('end-file', function()
|
||||
self.pressed = false
|
||||
self.heatmap = nil
|
||||
end)
|
||||
end
|
||||
|
||||
function Timeline:get_visibility()
|
||||
@@ -181,7 +189,7 @@ function Timeline:render()
|
||||
return
|
||||
end
|
||||
|
||||
if self.proximity_raw == 0 then
|
||||
if self.proximity_raw <= 0 then
|
||||
self.is_hovered = true
|
||||
end
|
||||
if visibility > 0 then
|
||||
@@ -257,7 +265,32 @@ function Timeline:render()
|
||||
ass:draw_stop()
|
||||
|
||||
-- Progress
|
||||
ass:rect(fax, fay, fbx, fby, {opacity = config.opacity.position})
|
||||
local function draw_progress()
|
||||
ass:rect(fax, fay, fbx, fby, {opacity = config.opacity.position})
|
||||
end
|
||||
|
||||
-- Youtube heatmap
|
||||
local function draw_heatmap()
|
||||
if options.timeline_heatmap ~= 'no' and self.heatmap and config.opacity.heatmap > 0 and visibility > 0 then
|
||||
local is_above = options.timeline_heatmap == 'above'
|
||||
local height = math.min(40, size / self.size * 40)
|
||||
local ax, ay = bax, is_above and (bay - height) or (bay + self.top_border)
|
||||
local bx, by = bbx, is_above and bay or bby
|
||||
local opts = {color = config.color.heatmap, opacity = config.opacity.heatmap * visibility}
|
||||
local clip_ay = is_above and (ay - 10) or ay
|
||||
opts.clip = string.format('\\clip(%d,%d,%d,%d)', ax, clip_ay, bx, by)
|
||||
ass:smooth_curve(ax, ay, bx, by, self.heatmap, opts)
|
||||
end
|
||||
end
|
||||
|
||||
-- Change draw order based on 'timeline_style' to keep the heatmap visible
|
||||
if is_line then
|
||||
draw_heatmap()
|
||||
draw_progress()
|
||||
else
|
||||
draw_progress()
|
||||
draw_heatmap()
|
||||
end
|
||||
|
||||
-- Uncached ranges
|
||||
if state.uncached_ranges then
|
||||
@@ -380,7 +413,7 @@ function Timeline:render()
|
||||
|
||||
-- Time values
|
||||
if text_opacity > 0 then
|
||||
local time_opts = {size = self.font_size, opacity = text_opacity, border = 2 * state.scale}
|
||||
local time_opts = {size = self.font_size, opacity = text_opacity, border = options.text_border * state.scale}
|
||||
-- Upcoming cache time
|
||||
local cache_duration = state.cache_duration and state.cache_duration / state.speed or nil
|
||||
if cache_duration and options.buffered_time_threshold > 0
|
||||
@@ -412,7 +445,7 @@ function Timeline:render()
|
||||
|
||||
-- Hovered time and chapter
|
||||
local rendered_thumbnail = false
|
||||
if (self.proximity_raw == 0 or self.pressed or hovered_chapter) and not Elements:v('speed', 'dragging') then
|
||||
if (self.proximity_raw <= 0 or self.pressed or hovered_chapter) and not Elements:v('speed', 'dragging') then
|
||||
local cursor_x = hovered_chapter and t2x(hovered_chapter.time) or cursor.x
|
||||
local hovered_seconds = hovered_chapter and hovered_chapter.time or self:get_time_at_x(cursor.x)
|
||||
|
||||
@@ -486,4 +519,4 @@ function Timeline:render()
|
||||
return ass
|
||||
end
|
||||
|
||||
return Timeline
|
||||
return Timeline
|
||||
@@ -21,11 +21,17 @@ function TopBar:init()
|
||||
self.current_chapter = nil
|
||||
|
||||
local function maximized_command()
|
||||
mp.command(state.fullormaxed and 'set fullscreen no;set window-maximized no' or 'set window-maximized yes')
|
||||
if state.platform == 'windows' then
|
||||
mp.command(state.border
|
||||
and (state.fullscreen and 'set fullscreen no;cycle window-maximized' or 'cycle window-maximized')
|
||||
or 'set window-maximized no;cycle fullscreen')
|
||||
else
|
||||
mp.command(state.fullormaxed and 'set fullscreen no;set window-maximized no' or 'set window-maximized yes')
|
||||
end
|
||||
end
|
||||
|
||||
local close = {icon = 'close', hover_bg = '2311e8', hover_fg = 'ffffff', command = function() mp.command('quit') end}
|
||||
local max = {icon = 'crop_square', command = maximized_command, is_max = true}
|
||||
local max = {icon = 'crop_square', command = maximized_command}
|
||||
local min = {icon = 'minimize', command = function() mp.command('cycle window-minimized') end}
|
||||
self.buttons = options.top_bar_controls == 'left' and {close, max, min} or {min, max, close}
|
||||
|
||||
@@ -237,13 +243,8 @@ function TopBar:render()
|
||||
end
|
||||
|
||||
for _, button in ipairs(self.buttons) do
|
||||
if button.is_max then
|
||||
button.icon = state.fullscreen and 'close_fullscreen' or
|
||||
(state.maximized and 'filter_none' or 'crop_square')
|
||||
end
|
||||
|
||||
local rect = {ax = button_ax, ay = ay, bx = button_ax + self.size, by = by}
|
||||
local is_hover = get_point_to_rectangle_proximity(cursor, rect) == 0
|
||||
local is_hover = get_point_to_rectangle_proximity(cursor, rect) <= 0
|
||||
local opacity = is_hover and 1 or config.opacity.controls
|
||||
local button_fg = is_hover and (button.hover_fg or bg) or fg
|
||||
local button_bg = is_hover and (button.hover_bg or fg) or bg
|
||||
@@ -290,7 +291,7 @@ function TopBar:render()
|
||||
bx = ax + rect_width,
|
||||
by = by - margin,
|
||||
}
|
||||
local opacity = get_point_to_rectangle_proximity(cursor, rect) == 0
|
||||
local opacity = get_point_to_rectangle_proximity(cursor, rect) <= 0
|
||||
and 1 or config.opacity.playlist_position
|
||||
if opacity > 0 then
|
||||
ass:rect(rect.ax, rect.ay, rect.bx, rect.by, {
|
||||
@@ -427,4 +428,4 @@ function TopBar:render()
|
||||
return ass
|
||||
end
|
||||
|
||||
return TopBar
|
||||
return TopBar
|
||||
@@ -0,0 +1,319 @@
|
||||
local Element = require('elements/Element')
|
||||
local dots = {'.', '..', '...'}
|
||||
|
||||
local function cleanup_output(output)
|
||||
return tostring(output):gsub('%c*\n%c*', '\n'):match('^[%s%c]*(.-)[%s%c]*$')
|
||||
end
|
||||
|
||||
---@class Updater : Element
|
||||
local Updater = class(Element)
|
||||
|
||||
function Updater:new() return Class.new(self) --[[@as Updater]] end
|
||||
function Updater:init()
|
||||
Element.init(self, 'updater', {render_order = 1000})
|
||||
self.output = nil
|
||||
self.title = ''
|
||||
self.state = 'circle' -- Also used as an icon name. 'pending' maps to 'spinner'.
|
||||
self.update_available = false
|
||||
|
||||
-- Buttons
|
||||
self.check_button = {method = 'check', title = t('Check for updates')}
|
||||
self.update_button = {method = 'update', title = t('Update uosc'), color = config.color.success}
|
||||
self.changelog_button = {method = 'open_changelog', title = t('Open changelog')}
|
||||
self.close_button = {method = 'destroy', title = t('Close') .. ' (Esc)', color = config.color.error}
|
||||
self.quit_button = {method = 'quit', title = t('Quit')}
|
||||
self.buttons = {self.check_button, self.close_button}
|
||||
self.selected_button_index = 1
|
||||
|
||||
-- Key bindings
|
||||
self:add_key_binding('right', 'select_next_button')
|
||||
self:add_key_binding('tab', 'select_next_button')
|
||||
self:add_key_binding('left', 'select_prev_button')
|
||||
self:add_key_binding('shift+tab', 'select_prev_button')
|
||||
self:add_key_binding('enter', 'activate_selected_button')
|
||||
self:add_key_binding('kp_enter', 'activate_selected_button')
|
||||
self:add_key_binding('esc', 'destroy')
|
||||
|
||||
Elements:maybe('curtain', 'register', self.id)
|
||||
self:check()
|
||||
end
|
||||
|
||||
function Updater:destroy()
|
||||
Elements:maybe('curtain', 'unregister', self.id)
|
||||
Element.destroy(self)
|
||||
end
|
||||
|
||||
function Updater:quit()
|
||||
mp.command('quit')
|
||||
end
|
||||
|
||||
function Updater:select_prev_button()
|
||||
self.selected_button_index = self.selected_button_index - 1
|
||||
if self.selected_button_index < 1 then self.selected_button_index = #self.buttons end
|
||||
request_render()
|
||||
end
|
||||
|
||||
function Updater:select_next_button()
|
||||
self.selected_button_index = self.selected_button_index + 1
|
||||
if self.selected_button_index > #self.buttons then self.selected_button_index = 1 end
|
||||
request_render()
|
||||
end
|
||||
|
||||
function Updater:activate_selected_button()
|
||||
local button = self.buttons[self.selected_button_index]
|
||||
if button then self[button.method](self) end
|
||||
end
|
||||
|
||||
---@param msg string
|
||||
function Updater:append_output(msg)
|
||||
self.output = (self.output or '') .. ass_escape('\n' .. cleanup_output(msg))
|
||||
request_render()
|
||||
end
|
||||
|
||||
---@param msg string
|
||||
function Updater:display_error(msg)
|
||||
self.state = 'error'
|
||||
self.title = t('An error has occurred.') .. ' ' .. t('See console for details.')
|
||||
self:append_output(msg)
|
||||
print(msg)
|
||||
end
|
||||
|
||||
function Updater:open_changelog()
|
||||
if self.state == 'pending' then return end
|
||||
|
||||
local url = 'https://github.com/tomasklaen/uosc/releases'
|
||||
|
||||
self:append_output('Opening URL: ' .. url)
|
||||
|
||||
call_ziggy_async({'open', url}, function(error)
|
||||
if error then
|
||||
self:display_error(error)
|
||||
return
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Updater:check()
|
||||
if self.state == 'pending' then return end
|
||||
self.state = 'pending'
|
||||
self.title = t('Checking for updates') .. '...'
|
||||
|
||||
local url = 'https://api.github.com/repos/tomasklaen/uosc/releases/latest'
|
||||
local headers = utils.format_json({
|
||||
Accept = 'application/vnd.github+json',
|
||||
})
|
||||
local args = {'http-get', '--headers', headers, url}
|
||||
|
||||
self:append_output('Fetching: ' .. url)
|
||||
|
||||
call_ziggy_async(args, function(error, response)
|
||||
if error then
|
||||
self:display_error(error)
|
||||
return
|
||||
end
|
||||
|
||||
release = utils.parse_json(type(response.body) == 'string' and response.body or '')
|
||||
if response.status == 200 and type(release) == 'table' and type(release.tag_name) == 'string' then
|
||||
self.update_available = config.version ~= release.tag_name
|
||||
self:append_output('Response: 200 OK')
|
||||
self:append_output('Current version: ' .. config.version)
|
||||
self:append_output('Latest version: ' .. release.tag_name)
|
||||
if self.update_available then
|
||||
self.state = 'upgrade'
|
||||
self.title = t('Update available')
|
||||
self.buttons = {self.update_button, self.changelog_button, self.close_button}
|
||||
self.selected_button_index = 1
|
||||
else
|
||||
self.state = 'done'
|
||||
self.title = t('Up to date')
|
||||
end
|
||||
else
|
||||
self:display_error('Response couldn\'t be parsed, is invalid, or not-OK status code.\nStatus: ' ..
|
||||
response.status .. '\nBody: ' .. response.body)
|
||||
end
|
||||
|
||||
request_render()
|
||||
end)
|
||||
end
|
||||
|
||||
function Updater:update()
|
||||
if self.state == 'pending' then return end
|
||||
self.state = 'pending'
|
||||
self.title = t('Updating uosc')
|
||||
self.output = nil
|
||||
request_render()
|
||||
|
||||
local config_dir = mp.command_native({'expand-path', '~~/'})
|
||||
|
||||
local function handle_result(success, result, error)
|
||||
if success and result and result.status == 0 then
|
||||
self.state = 'done'
|
||||
self.title = t('uosc has been installed. Restart mpv for it to take effect.')
|
||||
self.buttons = {self.quit_button, self.close_button}
|
||||
self.selected_button_index = 1
|
||||
else
|
||||
self.state = 'error'
|
||||
self.title = t('An error has occurred.') .. ' ' .. t('See above for clues.')
|
||||
end
|
||||
|
||||
local output = (result.stdout or '') .. '\n' .. (error or result.stderr or '')
|
||||
if state.platform == 'darwin' then
|
||||
output =
|
||||
'Self-updater is known not to work on MacOS.\nIf you know about a solution, please make an issue and share it with us!.\n' ..
|
||||
output
|
||||
end
|
||||
self:append_output(output)
|
||||
end
|
||||
|
||||
local function update(args)
|
||||
local env = utils.get_env_list()
|
||||
env[#env + 1] = 'MPV_CONFIG_DIR=' .. config_dir
|
||||
|
||||
mp.command_native_async({
|
||||
name = 'subprocess',
|
||||
capture_stderr = true,
|
||||
capture_stdout = true,
|
||||
playback_only = false,
|
||||
args = args,
|
||||
env = env,
|
||||
}, handle_result)
|
||||
end
|
||||
|
||||
if state.platform == 'windows' then
|
||||
local url = 'https://raw.githubusercontent.com/tomasklaen/uosc/HEAD/installers/windows.ps1'
|
||||
update({'powershell', '-NoProfile', '-Command', 'irm ' .. url .. ' | iex'})
|
||||
else
|
||||
-- Detect missing dependencies. We can't just let the process run and
|
||||
-- report an error, as on snap packages there's no error. Everything
|
||||
-- either exits with 0, or no helpful output/error message.
|
||||
local missing = {}
|
||||
|
||||
for _, name in ipairs({'curl', 'unzip'}) do
|
||||
local result = mp.command_native({
|
||||
name = 'subprocess',
|
||||
capture_stdout = true,
|
||||
playback_only = false,
|
||||
args = {'which', name},
|
||||
})
|
||||
local path = cleanup_output(result and result.stdout or '')
|
||||
if path == '' then
|
||||
missing[#missing + 1] = name
|
||||
end
|
||||
end
|
||||
|
||||
if #missing > 0 then
|
||||
local stderr = 'Missing dependencies: ' .. table.concat(missing, ', ')
|
||||
if config_dir:match('/snap/') then
|
||||
stderr = stderr ..
|
||||
'\nThis is a known error for mpv snap packages.\nYou can still update uosc by entering the Linux install command from uosc\'s readme into your terminal, it just can\'t be done this way.\nIf you know about a solution, please make an issue and share it with us!'
|
||||
end
|
||||
handle_result(false, {stderr = stderr})
|
||||
else
|
||||
local url = 'https://raw.githubusercontent.com/tomasklaen/uosc/HEAD/installers/unix.sh'
|
||||
update({'/bin/bash', '-c', 'source <(curl -fsSL ' .. url .. ')'})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Updater:render()
|
||||
local ass = assdraw.ass_new()
|
||||
|
||||
local text_size = math.min(20 * state.scale, display.height / 20)
|
||||
local icon_size = text_size * 2
|
||||
local center_x = round(display.width / 2)
|
||||
|
||||
local color = fg
|
||||
if self.state == 'done' or self.update_available then
|
||||
color = config.color.success
|
||||
elseif self.state == 'error' then
|
||||
color = config.color.error
|
||||
end
|
||||
|
||||
-- Divider
|
||||
local divider_width = round(math.min(500 * state.scale, display.width * 0.8))
|
||||
local divider_half, divider_border_half, divider_y = divider_width / 2, round(1 * state.scale), display.height * 0.65
|
||||
local divider_ay, divider_by = round(divider_y - divider_border_half), round(divider_y + divider_border_half)
|
||||
ass:rect(center_x - divider_half, divider_ay, center_x - icon_size, divider_by, {
|
||||
color = color, border = options.text_border * state.scale, border_color = bg, opacity = 0.5,
|
||||
})
|
||||
ass:rect(center_x + icon_size, divider_ay, center_x + divider_half, divider_by, {
|
||||
color = color, border = options.text_border * state.scale, border_color = bg, opacity = 0.5,
|
||||
})
|
||||
if self.state == 'pending' then
|
||||
ass:spinner(center_x, divider_y, icon_size, {
|
||||
color = fg, border = options.text_border * state.scale, border_color = bg,
|
||||
})
|
||||
else
|
||||
ass:icon(center_x, divider_y, icon_size * 0.8, self.state, {
|
||||
color = color, border = options.text_border * state.scale, border_color = bg,
|
||||
})
|
||||
end
|
||||
|
||||
-- Output
|
||||
local output = self.output or dots[math.ceil((mp.get_time() % 1) * #dots)]
|
||||
ass:txt(center_x, divider_y - icon_size, 2, output, {
|
||||
size = text_size, color = fg, border = options.text_border * state.scale, border_color = bg,
|
||||
})
|
||||
|
||||
-- Title
|
||||
ass:txt(center_x, divider_y + icon_size, 5, self.title, {
|
||||
size = text_size, bold = true, color = color, border = options.text_border * state.scale, border_color = bg,
|
||||
})
|
||||
|
||||
-- Buttons
|
||||
local outline = round(1 * state.scale)
|
||||
local spacing = outline * 9
|
||||
local padding = round(text_size * 0.5)
|
||||
|
||||
local text_opts = {size = text_size, bold = true}
|
||||
|
||||
-- Calculate button text widths
|
||||
local total_width = (#self.buttons - 1) * spacing
|
||||
for _, button in ipairs(self.buttons) do
|
||||
button.width = text_width(button.title, text_opts) + padding * 2
|
||||
total_width = total_width + button.width
|
||||
end
|
||||
|
||||
-- Render buttons
|
||||
local ay = round(divider_y + icon_size * 1.8)
|
||||
local ax = round(display.width / 2 - total_width / 2)
|
||||
local height = text_size + padding * 2
|
||||
for index, button in ipairs(self.buttons) do
|
||||
local rect = {
|
||||
ax = ax,
|
||||
ay = ay,
|
||||
bx = ax + button.width,
|
||||
by = ay + height,
|
||||
}
|
||||
ax = rect.bx + spacing
|
||||
local is_hovered = get_point_to_rectangle_proximity(cursor, rect) <= 0
|
||||
|
||||
-- Background
|
||||
ass:rect(rect.ax, rect.ay, rect.bx, rect.by, {
|
||||
color = button.color or fg,
|
||||
radius = state.radius,
|
||||
opacity = is_hovered and 1 or 0.8,
|
||||
})
|
||||
-- Selected outline
|
||||
if index == self.selected_button_index then
|
||||
ass:rect(rect.ax - outline * 4, rect.ay - outline * 4, rect.bx + outline * 4, rect.by + outline * 4, {
|
||||
border = outline,
|
||||
border_color = button.color or fg,
|
||||
radius = state.radius + outline * 4,
|
||||
opacity = {primary = 0, border = 0.5},
|
||||
})
|
||||
end
|
||||
-- Text
|
||||
local x, y = rect.ax + (rect.bx - rect.ax) / 2, rect.ay + (rect.by - rect.ay) / 2
|
||||
ass:txt(x, y, 5, button.title, {size = text_size, bold = true, color = fgt})
|
||||
|
||||
cursor:zone('primary_down', rect, self:create_action(button.method))
|
||||
|
||||
-- Select hovered button
|
||||
if is_hovered then self.selected_button_index = index end
|
||||
end
|
||||
|
||||
return ass
|
||||
end
|
||||
|
||||
return Updater
|
||||
@@ -242,6 +242,7 @@ end
|
||||
function Volume:on_display() self:update_dimensions() end
|
||||
function Volume:on_prop_border() self:update_dimensions() end
|
||||
function Volume:on_prop_title_bar() self:update_dimensions() end
|
||||
function Volume:on_prop_volume_max() self:update_dimensions() end
|
||||
function Volume:on_controls_reflow() self:update_dimensions() end
|
||||
function Volume:on_options() self:update_dimensions() end
|
||||
|
||||
@@ -280,4 +281,4 @@ function Volume:render()
|
||||
return ass
|
||||
end
|
||||
|
||||
return Volume
|
||||
return Volume
|
||||
@@ -26,10 +26,10 @@ function WindowBorder:render()
|
||||
local clip = '\\iclip(' .. self.size .. ',' .. self.size .. ',' ..
|
||||
(display.width - self.size) .. ',' .. (display.height - self.size) .. ')'
|
||||
ass:rect(0, 0, display.width + 1, display.height + 1, {
|
||||
color = bg, clip = clip, opacity = config.opacity.border,
|
||||
color = config.color.window_border, clip = clip, opacity = config.opacity.border,
|
||||
})
|
||||
return ass
|
||||
end
|
||||
end
|
||||
|
||||
return WindowBorder
|
||||
return WindowBorder
|
||||
Reference in New Issue
Block a user