diff options
Diffstat (limited to '.config/mpv/scripts/uosc_shared/elements/Controls.lua')
| -rw-r--r-- | .config/mpv/scripts/uosc_shared/elements/Controls.lua | 329 | 
1 files changed, 0 insertions, 329 deletions
| diff --git a/.config/mpv/scripts/uosc_shared/elements/Controls.lua b/.config/mpv/scripts/uosc_shared/elements/Controls.lua deleted file mode 100644 index 9a6be72..0000000 --- a/.config/mpv/scripts/uosc_shared/elements/Controls.lua +++ /dev/null @@ -1,329 +0,0 @@ -local Element = require('uosc_shared/elements/Element') -local Button = require('uosc_shared/elements/Button') -local CycleButton = require('uosc_shared/elements/CycleButton') -local Speed = require('uosc_shared/elements/Speed') - --- `scale` - `options.controls_size` scale factor. --- `ratio` - Width/height ratio of a static or dynamic element. --- `ratio_min` Min ratio for 'dynamic' sized element. ----@alias ControlItem {element?: Element; kind: string; sizing: 'space' | 'static' | 'dynamic'; scale: number; ratio?: number; ratio_min?: number; hide: boolean; dispositions?: table<string, boolean>} - ----@class Controls : Element -local Controls = class(Element) - -function Controls:new() return Class.new(self) --[[@as Controls]] end -function Controls:init() -	Element.init(self, 'controls') -	---@type ControlItem[] All control elements serialized from `options.controls`. -	self.controls = {} -	---@type ControlItem[] Only controls that match current dispositions. -	self.layout = {} - -	-- Serialize control elements -	local shorthands = { -		menu = 'command:menu:script-binding uosc/menu-blurred?Menu', -		subtitles = 'command:subtitles:script-binding uosc/subtitles#sub>0?Subtitles', -		audio = 'command:graphic_eq:script-binding uosc/audio#audio>1?Audio', -		['audio-device'] = 'command:speaker:script-binding uosc/audio-device?Audio device', -		video = 'command:theaters:script-binding uosc/video#video>1?Video', -		playlist = 'command:list_alt:script-binding uosc/playlist?Playlist', -		chapters = 'command:bookmark:script-binding uosc/chapters#chapters>0?Chapters', -		['editions'] = 'command:bookmarks:script-binding uosc/editions#editions>1?Editions', -		['stream-quality'] = 'command:high_quality:script-binding uosc/stream-quality?Stream quality', -		['open-file'] = 'command:file_open:script-binding uosc/open-file?Open file', -		['items'] = 'command:list_alt:script-binding uosc/items?Playlist/Files', -		prev = 'command:arrow_back_ios:script-binding uosc/prev?Previous', -		next = 'command:arrow_forward_ios:script-binding uosc/next?Next', -		first = 'command:first_page:script-binding uosc/first?First', -		last = 'command:last_page:script-binding uosc/last?Last', -		['loop-playlist'] = 'cycle:repeat:loop-playlist:no/inf!?Loop playlist', -		['loop-file'] = 'cycle:repeat_one:loop-file:no/inf!?Loop file', -		shuffle = 'toggle:shuffle:shuffle?Shuffle', -		fullscreen = 'cycle:crop_free:fullscreen:no/yes=fullscreen_exit!?Fullscreen', -	} - -	-- Parse out disposition/config pairs -	local items = {} -	local in_disposition = false -	local current_item = nil -	for c in options.controls:gmatch('.') do -		if not current_item then current_item = {disposition = '', config = ''} end -		if c == '<' and #current_item.config == 0 then in_disposition = true -		elseif c == '>' and #current_item.config == 0 then in_disposition = false -		elseif c == ',' and not in_disposition then -			items[#items + 1] = current_item -			current_item = nil -		else -			local prop = in_disposition and 'disposition' or 'config' -			current_item[prop] = current_item[prop] .. c -		end -	end -	items[#items + 1] = current_item - -	-- Create controls -	self.controls = {} -	for i, item in ipairs(items) do -		local config = shorthands[item.config] and shorthands[item.config] or item.config -		local config_tooltip = split(config, ' *%? *') -		local tooltip = config_tooltip[2] -		config = shorthands[config_tooltip[1]] -			and split(shorthands[config_tooltip[1]], ' *%? *')[1] or config_tooltip[1] -		local config_badge = split(config, ' *# *') -		config = config_badge[1] -		local badge = config_badge[2] -		local parts = split(config, ' *: *') -		local kind, params = parts[1], itable_slice(parts, 2) - -		-- Serialize dispositions -		local dispositions = {} -		for _, definition in ipairs(split(item.disposition, ' *, *')) do -			if #definition > 0 then -				local value = definition:sub(1, 1) ~= '!' -				local name = not value and definition:sub(2) or definition -				local prop = name:sub(1, 4) == 'has_' and name or 'is_' .. name -				dispositions[prop] = value -			end -		end - -		-- Convert toggles into cycles -		if kind == 'toggle' then -			kind = 'cycle' -			params[#params + 1] = 'no/yes!' -		end - -		-- Create a control element -		local control = {dispositions = dispositions, kind = kind} - -		if kind == 'space' then -			control.sizing = 'space' -		elseif kind == 'gap' then -			table_assign(control, {sizing = 'dynamic', scale = 1, ratio = params[1] or 0.3, ratio_min = 0}) -		elseif kind == 'command' then -			if #params ~= 2 then -				mp.error(string.format( -					'command button needs 2 parameters, %d received: %s', #params, table.concat(params, '/') -				)) -			else -				local element = Button:new('control_' .. i, { -					icon = params[1], -					anchor_id = 'controls', -					on_click = function() mp.command(params[2]) end, -					tooltip = tooltip, -					count_prop = 'sub', -				}) -				table_assign(control, {element = element, sizing = 'static', scale = 1, ratio = 1}) -				if badge then self:register_badge_updater(badge, element) end -			end -		elseif kind == 'cycle' then -			if #params ~= 3 then -				mp.error(string.format( -					'cycle button needs 3 parameters, %d received: %s', -					#params, table.concat(params, '/') -				)) -			else -				local state_configs = split(params[3], ' */ *') -				local states = {} - -				for _, state_config in ipairs(state_configs) do -					local active = false -					if state_config:sub(-1) == '!' then -						active = true -						state_config = state_config:sub(1, -2) -					end -					local state_params = split(state_config, ' *= *') -					local value, icon = state_params[1], state_params[2] or params[1] -					states[#states + 1] = {value = value, icon = icon, active = active} -				end - -				local element = CycleButton:new('control_' .. i, { -					prop = params[2], anchor_id = 'controls', states = states, tooltip = tooltip, -				}) -				table_assign(control, {element = element, sizing = 'static', scale = 1, ratio = 1}) -				if badge then self:register_badge_updater(badge, element) end -			end -		elseif kind == 'speed' then -			if not Elements.speed then -				local element = Speed:new({anchor_id = 'controls'}) -				table_assign(control, { -					element = element, sizing = 'dynamic', scale = params[1] or 1.3, ratio = 3.5, ratio_min = 2, -				}) -			else -				msg.error('there can only be 1 speed slider') -			end -		else -			msg.error('unknown element kind "' .. kind .. '"') -			break -		end - -		self.controls[#self.controls + 1] = control -	end - -	self:reflow() -end - -function Controls:reflow() -	-- Populate the layout only with items that match current disposition -	self.layout = {} -	for _, control in ipairs(self.controls) do -		local matches = true -		for prop, value in pairs(control.dispositions) do -			if state[prop] ~= value then -				matches = false -				break -			end -		end -		if control.element then control.element.enabled = matches end -		if matches then self.layout[#self.layout + 1] = control end -	end - -	self:update_dimensions() -	Elements:trigger('controls_reflow') -end - ----@param badge string ----@param element Element An element that supports `badge` property. -function Controls:register_badge_updater(badge, element) -	local prop_and_limit = split(badge, ' *> *') -	local prop, limit = prop_and_limit[1], tonumber(prop_and_limit[2] or -1) -	local observable_name, serializer, is_external_prop = prop, nil, false - -	if itable_index_of({'sub', 'audio', 'video'}, prop) then -		observable_name = 'track-list' -		serializer = function(value) -			local count = 0 -			for _, track in ipairs(value) do if track.type == prop then count = count + 1 end end -			return count -		end -	else -		local parts = split(prop, '@') -		-- Support both new `prop@owner` and old `@prop` syntaxes -		if #parts > 1 then prop, is_external_prop = parts[1] ~= '' and parts[1] or parts[2], true end -		serializer = function(value) return value and (type(value) == 'table' and #value or tostring(value)) or nil end -	end - -	local function handler(_, value) -		local new_value = serializer(value) --[[@as nil|string|integer]] -		local value_number = tonumber(new_value) -		if value_number then new_value = value_number > limit and value_number or nil end -		element.badge = new_value -		request_render() -	end - -	if is_external_prop then element['on_external_prop_' .. prop] = function(_, value) handler(prop, value) end -	else mp.observe_property(observable_name, 'native', handler) end -end - -function Controls:get_visibility() -	return (Elements.speed and Elements.speed.dragging) and 1 or Elements.timeline:get_is_hovered() -		and -1 or Element.get_visibility(self) -end - -function Controls:update_dimensions() -	local window_border = Elements.window_border.size -	local size = state.fullormaxed and options.controls_size_fullscreen or options.controls_size -	local spacing = options.controls_spacing -	local margin = options.controls_margin - -	-- Disable when not enough space -	local available_space = display.height - Elements.window_border.size * 2 -	if Elements.top_bar.enabled then available_space = available_space - Elements.top_bar.size end -	if Elements.timeline.enabled then available_space = available_space - Elements.timeline.size_max end -	self.enabled = available_space > size + 10 - -	-- Reset hide/enabled flags -	for c, control in ipairs(self.layout) do -		control.hide = false -		if control.element then control.element.enabled = self.enabled end -	end - -	if not self.enabled then return end - -	-- Container -	self.bx = display.width - window_border - margin -	self.by = (Elements.timeline.enabled and Elements.timeline.ay or display.height - window_border) - margin -	self.ax, self.ay = window_border + margin, self.by - size - -	-- Controls -	local available_width = self.bx - self.ax -	local statics_width = (#self.layout - 1) * spacing -	local min_content_width = statics_width -	local max_dynamics_width, dynamic_units, spaces = 0, 0, 0 - -	-- Calculate statics_width, min_content_width, and count spaces -	for c, control in ipairs(self.layout) do -		if control.sizing == 'space' then -			spaces = spaces + 1 -		elseif control.sizing == 'static' then -			local width = size * control.scale * control.ratio -			statics_width = statics_width + width -			min_content_width = min_content_width + width -		elseif control.sizing == 'dynamic' then -			min_content_width = min_content_width + size * control.scale * control.ratio_min -			max_dynamics_width = max_dynamics_width + size * control.scale * control.ratio -			dynamic_units = dynamic_units + control.scale * control.ratio -		end -	end - -	-- Hide & disable elements in the middle until we fit into available width -	if min_content_width > available_width then -		local i = math.ceil(#self.layout / 2 + 0.1) -		for a = 0, #self.layout - 1, 1 do -			i = i + (a * (a % 2 == 0 and 1 or -1)) -			local control = self.layout[i] - -			if control.kind ~= 'gap' and control.kind ~= 'space' then -				control.hide = true -				if control.element then control.element.enabled = false end -				if control.sizing == 'static' then -					local width = size * control.scale * control.ratio -					min_content_width = min_content_width - width - spacing -					statics_width = statics_width - width - spacing -				elseif control.sizing == 'dynamic' then -					min_content_width = min_content_width - size * control.scale * control.ratio_min - spacing -					max_dynamics_width = max_dynamics_width - size * control.scale * control.ratio -					dynamic_units = dynamic_units - control.scale * control.ratio -				end - -				if min_content_width < available_width then break end -			end -		end -	end - -	-- Lay out the elements -	local current_x = self.ax -	local width_for_dynamics = available_width - statics_width -	local space_width = (width_for_dynamics - max_dynamics_width) / spaces - -	for c, control in ipairs(self.layout) do -		if not control.hide then -			local sizing, element, scale, ratio = control.sizing, control.element, control.scale, control.ratio -			local width, height = 0, 0 - -			if sizing == 'space' then -				if space_width > 0 then width = space_width end -			elseif sizing == 'static' then -				height = size * scale -				width = height * ratio -			elseif sizing == 'dynamic' then -				height = size * scale -				width = max_dynamics_width < width_for_dynamics -					and height * ratio or width_for_dynamics * ((scale * ratio) / dynamic_units) -			end - -			local bx = current_x + width -			if element then element:set_coordinates(round(current_x), round(self.by - height), bx, self.by) end -			current_x = bx + spacing -		end -	end - -	Elements:update_proximities() -	request_render() -end - -function Controls:on_dispositions() self:reflow() end -function Controls:on_display() self:update_dimensions() end -function Controls:on_prop_border() self:update_dimensions() end -function Controls:on_prop_fullormaxed() self:update_dimensions() end -function Controls:on_timeline_enabled() self:update_dimensions() end - -return Controls | 
