feat: add tooltips
This commit is contained in:
@@ -32,8 +32,9 @@ timeline_chapters=dots
|
|||||||
timeline_chapters_opacity=0.2
|
timeline_chapters_opacity=0.2
|
||||||
timeline_chapters_width=6
|
timeline_chapters_width=6
|
||||||
|
|
||||||
# A comma delimited list of elements to construct the controls bar above the timeline. Set to `never` to disable.
|
# A comma delimited list of items to construct the controls bar above the timeline. Set to `never` to disable.
|
||||||
# Parameter spec: enclosed in `{}` means value, enclosed in `[]` means optional
|
# Parameter spec: enclosed in `{}` means value, enclosed in `[]` means optional
|
||||||
|
# Full item syntax: `[<[!]{disposition1}[,[!]{dispositionN}]>]element[:paramN][?{tooltip}]`
|
||||||
# Common properties:
|
# Common properties:
|
||||||
# `{icon}` - parameter used to specify an icon name (example: `face`)
|
# `{icon}` - parameter used to specify an icon name (example: `face`)
|
||||||
# - you can pick one here: https://fonts.google.com/icons?selected=Material+Icons
|
# - you can pick one here: https://fonts.google.com/icons?selected=Material+Icons
|
||||||
@@ -58,7 +59,7 @@ timeline_chapters_width=6
|
|||||||
# - fullscreen: `cycle:fullscreen:fullscreen:no/yes=fullscreen_exit!`
|
# - fullscreen: `cycle:fullscreen:fullscreen:no/yes=fullscreen_exit!`
|
||||||
# - loop-playlist: `cycle:repeat:loop-playlist:no/inf!`
|
# - loop-playlist: `cycle:repeat:loop-playlist:no/inf!`
|
||||||
# - `toggle:{icon}:{prop}`: `cycle:{icon}:{prop}:no/yes!`
|
# - `toggle:{icon}:{prop}`: `cycle:{icon}:{prop}:no/yes!`
|
||||||
# Element visibility control:
|
# Item visibility control:
|
||||||
# `<[!]{disposition1}[,[!]{dispositionN}]>` - optional prefix to control element's visibility
|
# `<[!]{disposition1}[,[!]{dispositionN}]>` - optional prefix to control element's visibility
|
||||||
# - `{disposition}` can be one of:
|
# - `{disposition}` can be one of:
|
||||||
# - `image` - true if current file is a single image
|
# - `image` - true if current file is a single image
|
||||||
@@ -72,6 +73,9 @@ timeline_chapters_width=6
|
|||||||
# - `<stream>stream-quality` - show stream quality button only for streams
|
# - `<stream>stream-quality` - show stream quality button only for streams
|
||||||
# - `<has_audio,!audio>audio` - show audio tracks button for all files that have
|
# - `<has_audio,!audio>audio` - show audio tracks button for all files that have
|
||||||
# an audio track, but are not exclusively audio only files
|
# an audio track, but are not exclusively audio only files
|
||||||
|
# Item tooltip:
|
||||||
|
# Place `?Tooltip text` after the element config to give it a tooltip.
|
||||||
|
# Example: `<stream>stream-quality?Stream quality`
|
||||||
controls=menu,gap,subtitles,<has_audio,!audio>audio,<stream>stream-quality,gap,loop-playlist,loop-file,space,speed,space,prev,items,next,shuffle,gap:1,fullscreen
|
controls=menu,gap,subtitles,<has_audio,!audio>audio,<stream>stream-quality,gap,loop-playlist,loop-file,space,speed,space,prev,items,next,shuffle,gap:1,fullscreen
|
||||||
controls_size=32
|
controls_size=32
|
||||||
controls_size_fullscreen=40
|
controls_size_fullscreen=40
|
||||||
|
160
scripts/uosc.lua
160
scripts/uosc.lua
@@ -374,11 +374,11 @@ end
|
|||||||
|
|
||||||
function text_width_estimate(text, font_size)
|
function text_width_estimate(text, font_size)
|
||||||
if not text or text == '' then return 0 end
|
if not text or text == '' then return 0 end
|
||||||
local text_width = 0
|
local text_length = 0
|
||||||
for _, _, width in utf8_iter(text) do
|
for _, _, length in utf8_iter(text) do
|
||||||
text_width = text_width + width
|
text_length = text_length + length
|
||||||
end
|
end
|
||||||
return text_width * font_size * options.font_height_to_letter_width_ratio
|
return text_length * font_size * options.font_height_to_letter_width_ratio
|
||||||
end
|
end
|
||||||
|
|
||||||
function utf8_iter(string)
|
function utf8_iter(string)
|
||||||
@@ -405,18 +405,18 @@ function utf8_iter(string)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function wrap_text(text, line_width_requested)
|
function wrap_text(text, target_line_length)
|
||||||
local line_width = 0
|
local line_length = 0
|
||||||
local wrap_at_chars = {' ', ' ', '-', '–'}
|
local wrap_at_chars = {' ', ' ', '-', '–'}
|
||||||
local remove_when_wrap = {' ', ' '}
|
local remove_when_wrap = {' ', ' '}
|
||||||
local lines = {}
|
local lines = {}
|
||||||
local line_start = 1
|
local line_start = 1
|
||||||
local before_end = nil
|
local before_end = nil
|
||||||
local before_width = 0
|
local before_length = 0
|
||||||
local before_line_start = 0
|
local before_line_start = 0
|
||||||
local before_removed_width = 0
|
local before_removed_length = 0
|
||||||
local max_width = 0
|
local max_length = 0
|
||||||
for char_start, count, char_width in utf8_iter(text) do
|
for char_start, count, char_length in utf8_iter(text) do
|
||||||
local char_end = char_start + count - 1
|
local char_end = char_start + count - 1
|
||||||
local char = text.sub(text, char_start, char_end)
|
local char = text.sub(text, char_start, char_end)
|
||||||
local can_wrap = false
|
local can_wrap = false
|
||||||
@@ -426,7 +426,7 @@ function wrap_text(text, line_width_requested)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
line_width = line_width + char_width
|
line_length = line_length + char_length
|
||||||
if can_wrap or (char_end == #text) then
|
if can_wrap or (char_end == #text) then
|
||||||
local remove = false
|
local remove = false
|
||||||
for _, c in ipairs(remove_when_wrap) do
|
for _, c in ipairs(remove_when_wrap) do
|
||||||
@@ -435,36 +435,36 @@ function wrap_text(text, line_width_requested)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local line_width_after_remove = line_width - (remove and char_width or 0)
|
local line_length_after_remove = line_length - (remove and char_length or 0)
|
||||||
if line_width_after_remove < line_width_requested then
|
if line_length_after_remove < target_line_length then
|
||||||
before_end = remove and char_start - 1 or char_end
|
before_end = remove and char_start - 1 or char_end
|
||||||
before_width = line_width_after_remove
|
before_length = line_length_after_remove
|
||||||
before_line_start = char_end + 1
|
before_line_start = char_end + 1
|
||||||
before_removed_width = remove and char_width or 0
|
before_removed_length = remove and char_length or 0
|
||||||
else
|
else
|
||||||
if (line_width_requested - before_width) <
|
if (target_line_length - before_length) <
|
||||||
(line_width_after_remove - line_width_requested) then
|
(line_length_after_remove - target_line_length) then
|
||||||
lines[#lines + 1] = text.sub(text, line_start, before_end)
|
lines[#lines + 1] = text.sub(text, line_start, before_end)
|
||||||
line_start = before_line_start
|
line_start = before_line_start
|
||||||
line_width = line_width - before_width - before_removed_width
|
line_length = line_length - before_length - before_removed_length
|
||||||
if before_width > max_width then max_width = before_width end
|
if before_length > max_length then max_length = before_length end
|
||||||
else
|
else
|
||||||
lines[#lines + 1] = text.sub(text, line_start, remove and char_start - 1 or char_end)
|
lines[#lines + 1] = text.sub(text, line_start, remove and char_start - 1 or char_end)
|
||||||
line_start = char_end + 1
|
line_start = char_end + 1
|
||||||
line_width = remove and line_width - char_width or line_width
|
line_length = remove and line_length - char_length or line_length
|
||||||
if line_width > max_width then max_width = line_width end
|
if line_length > max_length then max_length = line_length end
|
||||||
line_width = 0
|
line_length = 0
|
||||||
end
|
end
|
||||||
before_end = line_start
|
before_end = line_start
|
||||||
before_width = 0
|
before_length = 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #text >= line_start then
|
if #text >= line_start then
|
||||||
lines[#lines + 1] = string.sub(text, line_start)
|
lines[#lines + 1] = string.sub(text, line_start)
|
||||||
if line_width > max_width then max_width = line_width end
|
if line_length > max_length then max_length = line_length end
|
||||||
end
|
end
|
||||||
return table.concat(lines, '\n'), max_width
|
return table.concat(lines, '\n'), max_length
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Escape a string for verbatim display on the OSD
|
-- Escape a string for verbatim display on the OSD
|
||||||
@@ -723,6 +723,25 @@ function ass_mt:txt(x, y, align, value, opts)
|
|||||||
self.text = self.text .. '{' .. tags .. '}' .. value
|
self.text = self.text .. '{' .. tags .. '}' .. value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Tooltip
|
||||||
|
-- Draws text at center coordinate that shifts its position to not overflow the edges.
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@param align number
|
||||||
|
---@param value string|number
|
||||||
|
---@param opts? {size?: number; bold?: boolean; italic?: boolean; text_length_override?: number}
|
||||||
|
function ass_mt:tooltip(x, y, align, value, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
opts.size = opts.size or 16
|
||||||
|
opts.border = 1
|
||||||
|
opts.border_color = options.color_background
|
||||||
|
local text_width = opts.text_length_override
|
||||||
|
and opts.text_length_override * opts.size * options.font_height_to_letter_width_ratio
|
||||||
|
or text_width_estimate(value, opts.size)
|
||||||
|
local margin = text_width / 2
|
||||||
|
self:txt(math.max(margin, math.min(x, display.width - margin)), y, align, value, opts)
|
||||||
|
end
|
||||||
|
|
||||||
-- Rectangle
|
-- Rectangle
|
||||||
---@param ax number
|
---@param ax number
|
||||||
---@param ay number
|
---@param ay number
|
||||||
@@ -1817,8 +1836,7 @@ function render_timeline(this)
|
|||||||
if (this.proximity_raw == 0 or this.pressed) and not (Elements.speed and Elements.speed.dragging) then
|
if (this.proximity_raw == 0 or this.pressed) and not (Elements.speed and Elements.speed.dragging) then
|
||||||
-- add 0.5 to be in the middle of the pixel
|
-- add 0.5 to be in the middle of the pixel
|
||||||
local hovered_seconds = this:get_time_at_x(cursor.x + 0.5)
|
local hovered_seconds = this:get_time_at_x(cursor.x + 0.5)
|
||||||
local chapter_title = ''
|
local chapter_title, chapter_title_width = nil, nil
|
||||||
local chapter_title_width = 0
|
|
||||||
|
|
||||||
if (options.timeline_chapters ~= 'never' and state.chapters) then
|
if (options.timeline_chapters ~= 'never' and state.chapters) then
|
||||||
for i = #state.chapters, 1, -1 do
|
for i = #state.chapters, 1, -1 do
|
||||||
@@ -1833,23 +1851,15 @@ function render_timeline(this)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local time_formatted = format_time(hovered_seconds)
|
|
||||||
local margin_time = text_width_estimate(time_formatted, this.font_size) / 2
|
|
||||||
local margin_title = chapter_title_width * this.font_size * options.font_height_to_letter_width_ratio / 2
|
|
||||||
local opacity = math.min(options.timeline_opacity + 0.1, 1)
|
|
||||||
|
|
||||||
-- Chapter title
|
-- Chapter title
|
||||||
ass:txt(math.min(math.max(cursor.x, margin_title), display.width - margin_title), fay - this.font_size * 1.3,
|
if chapter_title then
|
||||||
2, chapter_title, {
|
ass:tooltip(cursor.x, fay - this.font_size * 1.4, 2, chapter_title, {
|
||||||
size = this.font_size, color = options.color_background_text, bold = true,
|
size = this.font_size, bold = true, text_length_override = chapter_title_width,
|
||||||
border = 1, border_color = options.color_background, opacity = opacity,
|
})
|
||||||
})
|
end
|
||||||
|
|
||||||
-- Timestamp
|
-- Timestamp
|
||||||
ass:txt(math.min(math.max(cursor.x, margin_time), display.width - margin_time), fay,
|
ass:tooltip(cursor.x, fay - 2, 2, format_time(hovered_seconds), {size = this.font_size})
|
||||||
2, time_formatted, {
|
|
||||||
size = this.font_size, color = options.color_background_text,
|
|
||||||
border = 1, border_color = options.color_background, opacity = opacity,
|
|
||||||
})
|
|
||||||
|
|
||||||
-- Cursor line
|
-- Cursor line
|
||||||
-- 0.5 to switch when the pixel is half filled in
|
-- 0.5 to switch when the pixel is half filled in
|
||||||
@@ -1908,7 +1918,7 @@ function render_top_bar(this)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Window title
|
-- Window title
|
||||||
if options.top_bar_title and (state.media_title or state.playlist_count > 1 ) then
|
if options.top_bar_title and (state.media_title or state.playlist_count > 1) then
|
||||||
local max_bx = this.title_bx - this.spacing
|
local max_bx = this.title_bx - this.spacing
|
||||||
local text = state.media_title or 'n/a'
|
local text = state.media_title or 'n/a'
|
||||||
if state.playlist_count > 1 then
|
if state.playlist_count > 1 then
|
||||||
@@ -1921,7 +1931,7 @@ function render_top_bar(this)
|
|||||||
local bg_ax = this.ax + bg_margin
|
local bg_ax = this.ax + bg_margin
|
||||||
local bg_bx = math.min(max_bx, this.ax + text_width_estimate(text, this.font_size) + padding * 2)
|
local bg_bx = math.min(max_bx, this.ax + text_width_estimate(text, this.font_size) + padding * 2)
|
||||||
ass:rect(bg_ax, this.ay + bg_margin, bg_bx, this.by - bg_margin, {
|
ass:rect(bg_ax, this.ay + bg_margin, bg_bx, this.by - bg_margin, {
|
||||||
color = options.color_background, opacity = visibility * 0.8, radius = 2
|
color = options.color_background, opacity = visibility * 0.8, radius = 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Text
|
-- Text
|
||||||
@@ -2433,13 +2443,14 @@ end
|
|||||||
|
|
||||||
-- Button
|
-- Button
|
||||||
---@param id string
|
---@param id string
|
||||||
---@param props {icon: string; on_click: function; anchor_id?: string; active?: boolean; foreground?: string; background?: string}
|
---@param props {icon: string; on_click: function; anchor_id?: string; active?: boolean; foreground?: string; background?: string; tooltip?: string}
|
||||||
function create_button(id, props)
|
function create_button(id, props)
|
||||||
return Element.new(id, {
|
return Element.new(id, {
|
||||||
enabled = true,
|
enabled = true,
|
||||||
anchor_id = props.anchor_id,
|
anchor_id = props.anchor_id,
|
||||||
icon = props.icon,
|
icon = props.icon,
|
||||||
active = props.active,
|
active = props.active,
|
||||||
|
tooltip = props.tooltip,
|
||||||
foreground = props.foreground or options.color_foreground,
|
foreground = props.foreground or options.color_foreground,
|
||||||
background = props.background or options.color_background,
|
background = props.background or options.color_background,
|
||||||
set_coordinates = function(this, ax, ay, bx, by)
|
set_coordinates = function(this, ax, ay, bx, by)
|
||||||
@@ -2463,13 +2474,22 @@ function create_button(id, props)
|
|||||||
local foreground = this.active and this.background or this.foreground
|
local foreground = this.active and this.background or this.foreground
|
||||||
local background = this.active and this.foreground or this.background
|
local background = this.active and this.foreground or this.background
|
||||||
|
|
||||||
-- Background on hover
|
-- Background
|
||||||
if is_hover_or_active then
|
if is_hover_or_active then
|
||||||
ass:rect(this.ax, this.ay, this.bx, this.by, {
|
ass:rect(this.ax, this.ay, this.bx, this.by, {
|
||||||
color = this.active and background or foreground, opacity = visibility * (this.active and 0.8 or 0.2), radius = 2,
|
color = this.active and background or foreground, radius = 2,
|
||||||
|
opacity = visibility * (this.active and 0.8 or 0.2),
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Tooltip on hover
|
||||||
|
if is_hover and this.tooltip then
|
||||||
|
ass:tooltip(
|
||||||
|
this.ax + (this.bx - this.ax) / 2, this.ay - this.font_size / 2, this.ay > 100 and 2 or 8,
|
||||||
|
this.tooltip
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
-- Icon
|
-- Icon
|
||||||
local x, y = round(this.ax + (this.bx - this.ax) / 2), round(this.ay + (this.by - this.ay) / 2)
|
local x, y = round(this.ax + (this.bx - this.ax) / 2), round(this.ay + (this.by - this.ay) / 2)
|
||||||
ass:icon(x, y, this.font_size, this.icon, {
|
ass:icon(x, y, this.font_size, this.icon, {
|
||||||
@@ -2484,13 +2504,13 @@ end
|
|||||||
-- Cycle prop button
|
-- Cycle prop button
|
||||||
---@alias CycleState {value: any; icon: string; active?: boolean}
|
---@alias CycleState {value: any; icon: string; active?: boolean}
|
||||||
---@param id string
|
---@param id string
|
||||||
---@param props {prop: string; states: CycleState[]; anchor_id?: string;}
|
---@param props {prop: string; states: CycleState[]; anchor_id?: string; tooltip?: string}
|
||||||
function create_cycle_button(id, props)
|
function create_cycle_button(id, props)
|
||||||
local prop = props.prop
|
local prop = props.prop
|
||||||
local states = props.states
|
local states = props.states
|
||||||
local current_state_index = 1
|
local current_state_index = 1
|
||||||
local button = create_button(id, {
|
local button = create_button(id, {
|
||||||
anchor_id = props.anchor_id, icon = states[1].icon, active = states[1].active,
|
anchor_id = props.anchor_id, icon = states[1].icon, active = states[1].active, tooltip = props.tooltip,
|
||||||
on_click = function()
|
on_click = function()
|
||||||
local new_state = states[current_state_index + 1] or states[1]
|
local new_state = states[current_state_index + 1] or states[1]
|
||||||
mp.set_property(prop, new_state.value)
|
mp.set_property(prop, new_state.value)
|
||||||
@@ -2784,24 +2804,24 @@ if options.controls and options.controls ~= 'never' then
|
|||||||
init = function(this) this:serialize() end,
|
init = function(this) this:serialize() end,
|
||||||
serialize = function(this)
|
serialize = function(this)
|
||||||
local shorthands = {
|
local shorthands = {
|
||||||
menu = 'command:menu:script-binding uosc/menu',
|
menu = 'command:menu:script-binding uosc/menu?Menu',
|
||||||
subtitles = 'command:subtitles:script-binding uosc/subtitles',
|
subtitles = 'command:subtitles:script-binding uosc/subtitles?Subtitles',
|
||||||
audio = 'command:audiotrack:script-binding uosc/audio',
|
audio = 'command:audiotrack:script-binding uosc/audio?Audio',
|
||||||
['audio-device'] = 'command:speaker:script-binding uosc/audio-device',
|
['audio-device'] = 'command:speaker:script-binding uosc/audio-device?Audio device',
|
||||||
video = 'command:theaters:script-binding uosc/video',
|
video = 'command:theaters:script-binding uosc/video?Video',
|
||||||
playlist = 'command:list_alt:script-binding uosc/playlist',
|
playlist = 'command:list_alt:script-binding uosc/playlist?Playlist',
|
||||||
chapters = 'command:bookmarks:script-binding uosc/chapters',
|
chapters = 'command:bookmarks:script-binding uosc/chapters?Chapters',
|
||||||
['stream-quality'] = 'command:deblur:script-binding uosc/stream-quality',
|
['stream-quality'] = 'command:deblur:script-binding uosc/stream-quality?Stream quality',
|
||||||
['open-file'] = 'command:file_open:script-binding uosc/open-file',
|
['open-file'] = 'command:file_open:script-binding uosc/open-file?Open file',
|
||||||
['items'] = 'command:list_alt:script-binding uosc/items',
|
['items'] = 'command:list_alt:script-binding uosc/items?Playlist/Files',
|
||||||
prev = 'command:arrow_back_ios:script-binding uosc/prev',
|
prev = 'command:arrow_back_ios:script-binding uosc/prev?Previous',
|
||||||
next = 'command:arrow_forward_ios:script-binding uosc/next',
|
next = 'command:arrow_forward_ios:script-binding uosc/next?Next',
|
||||||
first = 'command:first_page:script-binding uosc/first',
|
first = 'command:first_page:script-binding uosc/first?First',
|
||||||
last = 'command:last_page:script-binding uosc/last',
|
last = 'command:last_page:script-binding uosc/last?Last',
|
||||||
['loop-playlist'] = 'cycle:repeat:loop-playlist:no/inf!',
|
['loop-playlist'] = 'cycle:repeat:loop-playlist:no/inf!?Loop playlist',
|
||||||
['loop-file'] = 'cycle:repeat_one:loop-file:no/inf!',
|
['loop-file'] = 'cycle:repeat_one:loop-file:no/inf!?Loop file',
|
||||||
shuffle = 'toggle:shuffle:shuffle',
|
shuffle = 'toggle:shuffle:shuffle?Shuffle',
|
||||||
fullscreen = 'cycle:crop_free:fullscreen:no/yes=fullscreen_exit!',
|
fullscreen = 'cycle:crop_free:fullscreen:no/yes=fullscreen_exit!?Fullscreen',
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Parse configs
|
-- Parse configs
|
||||||
@@ -2839,6 +2859,9 @@ if options.controls and options.controls ~= 'never' then
|
|||||||
this.controls = {}
|
this.controls = {}
|
||||||
for i, item in ipairs(items) do
|
for i, item in ipairs(items) do
|
||||||
local config = shorthands[item.config] and shorthands[item.config] or item.config
|
local config = shorthands[item.config] and shorthands[item.config] or item.config
|
||||||
|
local config_tooltip = split(config, ' *%? *')
|
||||||
|
config = config_tooltip[1]
|
||||||
|
local tooltip = config_tooltip[2]
|
||||||
local parts = split(config, ' *: *')
|
local parts = split(config, ' *: *')
|
||||||
local kind, params = parts[1], itable_slice(parts, 2)
|
local kind, params = parts[1], itable_slice(parts, 2)
|
||||||
|
|
||||||
@@ -2865,6 +2888,7 @@ if options.controls and options.controls ~= 'never' then
|
|||||||
icon = params[1],
|
icon = params[1],
|
||||||
anchor_id = 'controls',
|
anchor_id = 'controls',
|
||||||
on_click = function() mp.command(params[2]) end,
|
on_click = function() mp.command(params[2]) end,
|
||||||
|
tooltip = tooltip,
|
||||||
})
|
})
|
||||||
this.controls[#this.controls + 1] = {
|
this.controls[#this.controls + 1] = {
|
||||||
kind = kind, element = element, sizing = 'static', scale = 1, ratio = 1,
|
kind = kind, element = element, sizing = 'static', scale = 1, ratio = 1,
|
||||||
@@ -2893,7 +2917,7 @@ if options.controls and options.controls ~= 'never' then
|
|||||||
end
|
end
|
||||||
|
|
||||||
local element = create_cycle_button('control_' .. i, {
|
local element = create_cycle_button('control_' .. i, {
|
||||||
prop = params[2], anchor_id = 'controls', states = states,
|
prop = params[2], anchor_id = 'controls', states = states, tooltip = tooltip,
|
||||||
})
|
})
|
||||||
this.controls[#this.controls + 1] = {
|
this.controls[#this.controls + 1] = {
|
||||||
kind = kind, element = element, sizing = 'static', scale = 1, ratio = 1,
|
kind = kind, element = element, sizing = 'static', scale = 1, ratio = 1,
|
||||||
|
Reference in New Issue
Block a user