mirror of
https://github.com/UltimateMenu/UltimateMenu.git
synced 2026-10-10 03:43:09 +08:00
139 lines
6.2 KiB
Lua
139 lines
6.2 KiB
Lua
-- Companion information panel for the Nenyoo edition of Ultimate Menu.
|
|
-- The menu adapter publishes text on the script thread. Draw only the
|
|
-- published strings here; overlay.on_draw runs on Nenyoo's render thread.
|
|
return function(compat)
|
|
local scroll_by_page = {}
|
|
local last_page
|
|
|
|
local function clamp(value, low, high)
|
|
return math.max(low, math.min(high, value))
|
|
end
|
|
|
|
local function wrap_paragraph(value, max_width, output, heading, color)
|
|
local line = ""
|
|
for word in value:gmatch("%S+") do
|
|
local candidate = line == "" and word or line .. " " .. word
|
|
if line ~= "" and text.width(font.small, candidate) > max_width then
|
|
output[#output + 1] = {text = line, heading = heading, color = color}
|
|
line = word
|
|
else
|
|
line = candidate
|
|
end
|
|
end
|
|
if line ~= "" then output[#output + 1] = {text = line, heading = heading, color = color} end
|
|
end
|
|
|
|
local function make_lines(rows, max_width)
|
|
local lines = {}
|
|
for _, row in ipairs(rows) do
|
|
local value = tostring(row.text or ""):gsub("\r", "")
|
|
if value == "" then
|
|
lines[#lines + 1] = {gap = 8}
|
|
else
|
|
local compact = value:gsub("%s+", " "):match("^%s*(.-)%s*$")
|
|
local heading = #compact <= 38 and (compact:sub(-1) == ":" or compact == "Preps")
|
|
for paragraph in (value .. "\n"):gmatch("(.-)\n") do
|
|
if paragraph:match("%S") then wrap_paragraph(paragraph, max_width, lines, heading, row.color)
|
|
else lines[#lines + 1] = {gap = 8} end
|
|
end
|
|
lines[#lines + 1] = {gap = heading and 7 or 11}
|
|
end
|
|
if row.tooltip then
|
|
wrap_paragraph(row.tooltip:gsub("%s+", " "), max_width, lines, false, nil)
|
|
lines[#lines + 1] = {gap = 11}
|
|
end
|
|
end
|
|
while #lines > 0 and lines[#lines].gap do lines[#lines] = nil end
|
|
return lines
|
|
end
|
|
|
|
overlay.on_draw("ultimate_menu_information", function()
|
|
if not menu.is_visible() then return end
|
|
local id = menu.page_id()
|
|
local info = compat.info_for_page(id)
|
|
if not info or #info.rows == 0 then return end
|
|
local sw, sh = ctx.screen_w(), ctx.screen_h()
|
|
local bx, by, bw = menu.bounds()
|
|
if not bw or bw <= 0 then return end
|
|
|
|
local gap, pad, header_h = 14, 14, 36
|
|
local width = math.min(420, math.max(280, sw * 0.30))
|
|
local side = bx + bw + gap + width <= sw - 8 or bx - gap - width >= 8
|
|
if not side then width = math.min(600, sw - 16) end
|
|
local x
|
|
if side and bx + bw + gap + width <= sw - 8 then x = bx + bw + gap
|
|
elseif side then x = bx - gap - width
|
|
else x = clamp(bx + (bw - width) * 0.5, 8, sw - width - 8) end
|
|
|
|
local lines = make_lines(info.rows, width - pad * 2)
|
|
if #lines == 0 then return end
|
|
local line_h = text.height(font.small) + 4
|
|
local content_h = 0
|
|
for _, line in ipairs(lines) do content_h = content_h + (line.gap or line_h) end
|
|
local desired_h = header_h + pad * 2 + content_h
|
|
local height = math.min(desired_h, sh - 16, 650)
|
|
local y = clamp(by, 8, sh - height - 8)
|
|
if not side then
|
|
local _, content_y, _, content_height = menu.content_rect()
|
|
local below = content_y + content_height + 56
|
|
local space_below = sh - below - 8
|
|
if content_height > 0 and space_below >= 170 then
|
|
y = below
|
|
height = math.min(height, space_below)
|
|
elseif by >= 180 then
|
|
height = math.min(height, by - 16)
|
|
y = by - height - 8
|
|
end
|
|
end
|
|
local viewport_h = height - header_h - pad * 2
|
|
if viewport_h <= line_h then return end
|
|
|
|
if last_page ~= id then scroll_by_page[id] = 0; last_page = id end
|
|
local max_scroll = math.max(0, content_h - viewport_h)
|
|
local scroll = clamp(scroll_by_page[id] or 0, 0, max_scroll)
|
|
local mx, my = input.mouse_x(), input.mouse_y()
|
|
if mx >= x and mx <= x + width and my >= y and my <= y + height then
|
|
scroll = clamp(scroll - input.mouse_wheel() * line_h * 3, 0, max_scroll)
|
|
end
|
|
scroll_by_page[id] = scroll
|
|
|
|
local ar, ag, ab = theme.accent()
|
|
draw.rect(x, y, x + width, y + height, 12, 15, 22, 232, 7)
|
|
draw.rect(x, y, x + width, y + header_h, 24, 32, 44, 245, 7)
|
|
draw.rect(x, y + header_h - 2, x + width, y + header_h, ar, ag, ab, 255)
|
|
draw.rect_outline(x, y, x + width, y + height, ar, ag, ab, 125, 7)
|
|
text.draw_ellipsis(font.item, x + pad, y + (header_h - text.height(font.item)) * 0.5,
|
|
240, 245, 250, 255, info.title, width - pad * 2)
|
|
|
|
local top = y + header_h + pad
|
|
draw.push_clip(x + pad, top, x + width - pad, top + viewport_h)
|
|
local cursor = top - scroll
|
|
for _, line in ipairs(lines) do
|
|
if line.gap then cursor = cursor + line.gap
|
|
else
|
|
if cursor + line_h >= top and cursor <= top + viewport_h then
|
|
local r, g, b = 220, 228, 238
|
|
if line.heading then r, g, b = ar, ag, ab end
|
|
if line.color then
|
|
local function channel(value)
|
|
value = value or 1
|
|
return clamp(math.floor((value <= 1 and value * 255 or value) + 0.5), 0, 255)
|
|
end
|
|
r, g, b = channel(line.color[1]), channel(line.color[2]), channel(line.color[3])
|
|
end
|
|
text.draw(font.small, x + pad, cursor, r, g, b, 245, line.text)
|
|
end
|
|
cursor = cursor + line_h
|
|
end
|
|
end
|
|
draw.pop_clip()
|
|
if max_scroll > 0 then
|
|
local track_top, track_height = top, viewport_h
|
|
local thumb_h = math.max(22, track_height * viewport_h / content_h)
|
|
local thumb_y = track_top + (track_height - thumb_h) * scroll / max_scroll
|
|
draw.rect(x + width - 5, thumb_y, x + width - 2, thumb_y + thumb_h,
|
|
ar, ag, ab, 190, 2)
|
|
end
|
|
end)
|
|
end
|