Initial commit

This commit is contained in:
2026-03-15 14:54:49 +03:00
commit 64f8029c06
4027 changed files with 254888 additions and 0 deletions

View File

@@ -0,0 +1,511 @@
Mantle.color_dark = {
header = Color(40, 40, 40), -- верхняя панель
header_text = Color(100, 100, 100), -- цвет элементов в заголовке
background = Color(25, 25, 25), -- фон
background_alpha = Color(25, 25, 25, 210), -- фон с прозрачностью
background_panelpopup = Color(20, 20, 20, 150), -- фон для DermaMenu
button = Color(54, 54, 54), -- кнопка
button_shadow = Color(0, 0, 0, 25), -- тень кнопки для градиента
button_hovered = Color(60, 60, 62), -- кнопка при наведении
category = Color(50, 50, 50), -- категория
category_opened = Color(50, 50, 50, 0), -- категория открыта
theme = Color(106, 108, 197), -- тема интерфейса
panel = { -- варианты цветов для панели
Color(60, 60, 60),
Color(50, 50, 50),
Color(80, 80, 80)
},
toggle = Color(56, 56, 56), -- тумблер
focus_panel = Color(46, 46, 46), -- универсальный цвет для элементов
hover = Color(60, 65, 80), -- универсальное выделение
window_shadow = Color(0, 0, 0, 100), -- тень окна
gray = Color(150, 150, 150, 220),
text = Color(255, 255, 255)
}
Mantle.color_dark.panel_alpha = { -- прозрачные панели
ColorAlpha(Mantle.color_dark.panel[1], 150),
ColorAlpha(Mantle.color_dark.panel[2], 150),
ColorAlpha(Mantle.color_dark.panel[3], 150)
}
-- Тёмная палитра (монотонная)
Mantle.color_dark_mono = table.Copy(Mantle.color_dark)
Mantle.color_dark_mono.theme = Color(121, 121, 121)
-- Светлая палитра
Mantle.color_light = {
header = Color(240, 240, 240),
header_text = Color(150, 150, 150),
background = Color(255, 255, 255),
background_alpha = Color(255, 255, 255, 170),
background_panelpopup = Color(245, 245, 245, 150),
button = Color(235, 235, 235),
button_shadow = Color(0, 0, 0, 15),
button_hovered = Color(196, 199, 218),
category = Color(240, 240, 245),
category_opened = Color(240, 240, 245, 0),
theme = Color(106, 108, 197),
panel = {
Color(250, 250, 255),
Color(240, 240, 245),
Color(230, 230, 235)
},
toggle = Color(220, 220, 230),
focus_panel = Color(245, 245, 255),
hover = Color(235, 240, 255),
window_shadow = Color(0, 0, 0, 50),
gray = Color(130, 130, 130, 220),
text = Color(20, 20, 20)
}
Mantle.color_light.panel_alpha = {
ColorAlpha(Mantle.color_light.panel[1], 120),
ColorAlpha(Mantle.color_light.panel[2], 120),
ColorAlpha(Mantle.color_light.panel[3], 120)
}
-- Синяя палитра
Mantle.color_blue = {
header = Color(36, 48, 66),
header_text = Color(109, 129, 159),
background = Color(24, 28, 38),
background_alpha = Color(24, 28, 38, 210),
background_panelpopup = Color(20, 24, 32, 150),
button = Color(38, 54, 82),
button_shadow = Color(18, 22, 32, 35),
button_hovered = Color(47, 69, 110),
category = Color(34, 48, 72),
category_opened = Color(34, 48, 72, 0),
theme = Color(80, 160, 220),
panel = {
Color(34, 48, 72),
Color(38, 54, 82),
Color(70, 120, 180)
},
toggle = Color(34, 44, 66),
focus_panel = Color(48, 72, 90),
hover = Color(80, 160, 220, 90),
window_shadow = Color(18, 22, 32, 100),
gray = Color(150, 170, 190, 200),
text = Color(210, 220, 235)
}
Mantle.color_blue.panel_alpha = {
ColorAlpha(Mantle.color_blue.panel[1], 110),
ColorAlpha(Mantle.color_blue.panel[2], 110),
ColorAlpha(Mantle.color_blue.panel[3], 110)
}
-- Красная палитра
Mantle.color_red = {
header = Color(54, 36, 36),
header_text = Color(159, 109, 109),
background = Color(32, 24, 24),
background_alpha = Color(32, 24, 24, 210),
background_panelpopup = Color(28, 20, 20, 150),
button = Color(66, 38, 38),
button_shadow = Color(32, 18, 18, 35),
button_hovered = Color(97, 50, 50),
category = Color(62, 34, 34),
category_opened = Color(62, 34, 34, 0),
theme = Color(180, 80, 80),
panel = {
Color(62, 34, 34),
Color(66, 38, 38),
Color(140, 70, 70)
},
toggle = Color(60, 34, 34),
focus_panel = Color(72, 48, 48),
hover = Color(180, 80, 80, 90),
window_shadow = Color(32, 18, 18, 100),
gray = Color(180, 150, 150, 200),
text = Color(235, 210, 210)
}
Mantle.color_red.panel_alpha = {
ColorAlpha(Mantle.color_red.panel[1], 110),
ColorAlpha(Mantle.color_red.panel[2], 110),
ColorAlpha(Mantle.color_red.panel[3], 110)
}
-- Зелёная палитра
Mantle.color_green = {
header = Color(36, 54, 40),
header_text = Color(109, 159, 109),
background = Color(24, 32, 26),
background_alpha = Color(24, 32, 26, 210),
background_panelpopup = Color(20, 28, 22, 150),
button = Color(38, 66, 48),
button_shadow = Color(18, 32, 22, 35),
button_hovered = Color(48, 88, 62),
category = Color(34, 62, 44),
category_opened = Color(34, 62, 44, 0),
theme = Color(80, 180, 120),
panel = {
Color(34, 62, 44),
Color(38, 66, 48),
Color(70, 140, 90)
},
toggle = Color(34, 60, 44),
focus_panel = Color(48, 72, 58),
hover = Color(80, 180, 120, 90),
window_shadow = Color(18, 32, 22, 100),
gray = Color(150, 180, 150, 200),
text = Color(210, 235, 210)
}
Mantle.color_green.panel_alpha = {
ColorAlpha(Mantle.color_green.panel[1], 110),
ColorAlpha(Mantle.color_green.panel[2], 110),
ColorAlpha(Mantle.color_green.panel[3], 110)
}
-- Оранжевая палитра
Mantle.color_orange = {
header = Color(70, 35, 10),
header_text = Color(250, 230, 210),
background = Color(255, 250, 240),
background_alpha = Color(255, 250, 240, 220),
background_panelpopup = Color(255, 245, 235, 160),
button = Color(184, 122, 64),
button_shadow = Color(20, 10, 0, 30),
button_hovered = Color(197, 129, 65),
category = Color(255, 245, 235),
category_opened = Color(255, 245, 235, 0),
theme = Color(245, 130, 50),
panel = {
Color(255, 250, 240),
Color(250, 220, 180),
Color(235, 150, 90)
},
toggle = Color(143, 121, 104),
focus_panel = Color(255, 240, 225),
hover = Color(255, 165, 80, 90),
window_shadow = Color(20, 8, 0, 100),
gray = Color(180, 161, 150, 200),
text = Color(45, 20, 10)
}
Mantle.color_orange.panel_alpha = {
ColorAlpha(Mantle.color_orange.panel[1], 120),
ColorAlpha(Mantle.color_orange.panel[2], 120),
ColorAlpha(Mantle.color_orange.panel[3], 120)
}
-- Фиолетовая палитра
Mantle.color_purple = {
header = Color(40, 36, 56),
header_text = Color(150, 140, 180),
background = Color(25, 22, 30),
background_alpha = Color(25, 22, 30, 210),
background_panelpopup = Color(28, 24, 40, 150),
button = Color(58, 52, 76),
button_shadow = Color(8, 6, 20, 30),
button_hovered = Color(74, 64, 105),
category = Color(46, 40, 60),
category_opened = Color(46, 40, 60, 0),
theme = Color(138, 114, 219),
panel = {
Color(56, 48, 76),
Color(44, 36, 64),
Color(120, 90, 200)
},
toggle = Color(43, 39, 53),
focus_panel = Color(48, 42, 62),
hover = Color(138, 114, 219, 90),
window_shadow = Color(8, 6, 20, 100),
gray = Color(140, 128, 148, 220),
text = Color(245, 240, 255)
}
Mantle.color_purple.panel_alpha = {
ColorAlpha(Mantle.color_purple.panel[1], 150),
ColorAlpha(Mantle.color_purple.panel[2], 150),
ColorAlpha(Mantle.color_purple.panel[3], 150)
}
-- Кофейная палитра
Mantle.color_coffee = {
header = Color(67, 48, 36),
header_text = Color(210, 190, 170),
background = Color(45, 32, 25),
background_alpha = Color(45, 32, 25, 215),
background_panelpopup = Color(38, 28, 22, 150),
button = Color(84, 60, 45),
button_shadow = Color(20, 10, 5, 40),
button_hovered = Color(100, 75, 55),
category = Color(72, 54, 42),
category_opened = Color(72, 54, 42, 0),
theme = Color(150, 110, 75),
panel = {
Color(68, 50, 40),
Color(90, 65, 50),
Color(150, 110, 75)
},
toggle = Color(53, 40, 31),
focus_panel = Color(70, 55, 40),
hover = Color(150, 110, 75, 90),
window_shadow = Color(15, 10, 5, 100),
gray = Color(180, 150, 130, 200),
text = Color(235, 225, 210)
}
Mantle.color_coffee.panel_alpha = {
ColorAlpha(Mantle.color_coffee.panel[1], 110),
ColorAlpha(Mantle.color_coffee.panel[2], 110),
ColorAlpha(Mantle.color_coffee.panel[3], 110)
}
-- Ледяная палитра
Mantle.color_ice = {
header = Color(190, 225, 250),
header_text = Color(68, 104, 139),
background = Color(235, 245, 255),
background_alpha = Color(235, 245, 255, 200),
background_panelpopup = Color(220, 235, 245, 150),
button = Color(145, 185, 225),
button_shadow = Color(80, 110, 140, 40),
button_hovered = Color(170, 210, 255),
category = Color(200, 225, 245),
category_opened = Color(200, 225, 245, 0),
theme = Color(100, 170, 230),
panel = {
Color(146, 186, 211),
Color(107, 157, 190),
Color(74, 132, 184)
},
toggle = Color(168, 194, 219),
focus_panel = Color(205, 230, 245),
hover = Color(100, 170, 230, 80),
window_shadow = Color(60, 100, 140, 100),
gray = Color(92, 112, 133, 200),
text = Color(20, 35, 50)
}
Mantle.color_ice.panel_alpha = {
ColorAlpha(Mantle.color_ice.panel[1], 120),
ColorAlpha(Mantle.color_ice.panel[2], 120),
ColorAlpha(Mantle.color_ice.panel[3], 120)
}
-- Винная палитра
Mantle.color_wine = {
header = Color(59, 42, 53),
header_text = Color(246, 242, 246),
background = Color(31, 23, 22),
background_alpha = Color(31, 23, 22, 210),
background_panelpopup = Color(36, 28, 28, 150),
button = Color(79, 50, 60),
button_shadow = Color(10, 6, 18, 30),
button_hovered = Color(90, 52, 65),
category = Color(79, 50, 60),
category_opened = Color(79, 50, 60, 0),
theme = Color(148, 61, 91),
panel = {
Color(79, 50, 60),
Color(63, 44, 48),
Color(160, 85, 143)
},
toggle = Color(63, 40, 47),
focus_panel = Color(70, 48, 58),
hover = Color(192, 122, 217, 90),
window_shadow = Color(10, 6, 20, 100),
gray = Color(170, 150, 160, 200),
text = Color(246, 242, 246)
}
Mantle.color_wine.panel_alpha = {
ColorAlpha(Mantle.color_wine.panel[1], 150),
ColorAlpha(Mantle.color_wine.panel[2], 150),
ColorAlpha(Mantle.color_wine.panel[3], 150)
}
-- Фиалковая палитра
Mantle.color_violet = {
header = Color(49, 50, 68),
header_text = Color(238, 244, 255),
background = Color(22, 24, 35),
background_alpha = Color(22, 24, 35, 210),
background_panelpopup = Color(36, 40, 56, 150),
button = Color(58, 64, 84),
button_shadow = Color(8, 6, 18, 30),
button_hovered = Color(64, 74, 104),
category = Color(58, 64, 84),
category_opened = Color(58, 64, 84, 0),
theme = Color(159, 180, 255),
panel = {
Color(58, 64, 84),
Color(48, 52, 72),
Color(109, 136, 255)
},
toggle = Color(46, 51, 66),
focus_panel = Color(56, 62, 86),
hover = Color(159, 180, 255, 90),
window_shadow = Color(8, 6, 20, 100),
gray = Color(147, 147, 184, 200),
text = Color(238, 244, 255)
}
Mantle.color_violet.panel_alpha = {
ColorAlpha(Mantle.color_violet.panel[1], 150),
ColorAlpha(Mantle.color_violet.panel[2], 150),
ColorAlpha(Mantle.color_violet.panel[3], 150)
}
-- Моховая палитра
Mantle.color_moss = {
header = Color(42, 50, 36),
header_text = Color(232, 244, 235),
background = Color(14, 16, 12),
background_alpha = Color(14, 16, 12, 210),
background_panelpopup = Color(24, 28, 22, 150),
button = Color(64, 82, 60),
button_shadow = Color(6, 8, 6, 30),
button_hovered = Color(74, 99, 68),
category = Color(46, 64, 44),
category_opened = Color(46, 64, 44, 0),
theme = Color(110, 160, 90),
panel = {
Color(40, 56, 40),
Color(66, 86, 66),
Color(110, 160, 90)
},
toggle = Color(35, 44, 34),
focus_panel = Color(46, 58, 44),
hover = Color(110, 160, 90, 90),
window_shadow = Color(0, 0, 0, 100),
gray = Color(148, 165, 140, 220),
text = Color(232, 244, 235)
}
Mantle.color_moss.panel_alpha = {
ColorAlpha(Mantle.color_moss.panel[1], 150),
ColorAlpha(Mantle.color_moss.panel[2], 150),
ColorAlpha(Mantle.color_moss.panel[3], 150)
}
-- Коралловая палитра
Mantle.color_coral = {
header = Color(52, 32, 36),
header_text = Color(255, 243, 242),
background = Color(18, 14, 16),
background_alpha = Color(18, 14, 16, 210),
background_panelpopup = Color(30, 22, 24, 150),
button = Color(116, 66, 61),
button_shadow = Color(8, 4, 6, 30),
button_hovered = Color(134, 73, 68),
category = Color(74, 40, 42),
category_opened = Color(74, 40, 42, 0),
theme = Color(255, 120, 90),
panel = {
Color(66, 38, 40),
Color(120, 60, 56),
Color(240, 120, 90)
},
toggle = Color(58, 39, 37),
focus_panel = Color(72, 42, 44),
hover = Color(255, 120, 90, 90),
window_shadow = Color(0, 0, 0, 100),
gray = Color(167, 136, 136, 220),
text = Color(255, 243, 242)
}
Mantle.color_coral.panel_alpha = {
ColorAlpha(Mantle.color_coral.panel[1], 150),
ColorAlpha(Mantle.color_coral.panel[2], 150),
ColorAlpha(Mantle.color_coral.panel[3], 150)
}

View File

@@ -0,0 +1,326 @@
Mantle.func = {
sw = ScrW(),
sh = ScrH(),
ents_scales = {},
}
local function CreateFonts()
local function CreateFont(name, font_name, size)
surface.CreateFont(name, {
font = font_name,
size = size,
extended = true
})
end
local old_surface_SetFont = surface.SetFont
local createdFonts = {
['Fated.16'] = true
}
CreateFont('Fated.16', 'Montserrat Medium', 16)
function surface.SetFont(font)
if type(font) != 'string' then
if font == nil then
ErrorNoHalt('surface.SetFont called with nil! Using fallback font')
old_surface_SetFont('DermaDefault')
return
end
old_surface_SetFont(font)
return
end
if !createdFonts[font] and font:match('^Fated%.') then
local size, isBold = font:match('^Fated%.(%d+)(b?)$')
if size then
size = tonumber(size)
local fontFamily = isBold == 'b' and 'Montserrat Bold' or 'Montserrat Medium'
CreateFont(font, fontFamily, size)
createdFonts[font] = true
end
end
old_surface_SetFont(font)
end
end
local math_sin = math.sin
local math_clamp = math.Clamp
local math_abs = math.abs
local function CreateFunc()
local mat_blur = Material('pp/blurscreen')
--[[
Отрисовка размытия у панели.
Применяется в функциях отрисовки (например Paint)
]]--
function Mantle.func.blur(panel)
local x, y = panel:LocalToScreen(0, 0)
surface.SetDrawColor(color_white)
surface.SetMaterial(mat_blur)
for i = 1, 6 do
if !mat_blur:GetFloat('$blur') then
mat_blur:SetFloat('$blur', i)
mat_blur:Recompute()
end
render.UpdateScreenEffectTexture()
surface.DrawTexturedRect(-x, -y, Mantle.func.sw, Mantle.func.sh)
end
end
local listGradients = {
Material('vgui/gradient_up'),
Material('vgui/gradient_down'),
Material('vgui/gradient-l'),
Material('vgui/gradient-r')
}
--[[
Отрисовка градиента
]]--
function Mantle.func.gradient(_x, _y, _w, _h, direction, color_shadow, radius, flags)
radius = radius and radius or 0
RNDX.DrawMaterial(radius, _x, _y, _w, _h, color_shadow, listGradients[direction], flags)
end
function Mantle.func.sound(path)
surface.PlaySound(path or 'mantle/btn_click.ogg')
end
Mantle.func.w_save = {}
Mantle.func.h_save = {}
--[[
Получение относительной ширины (на основе 1920)
При указании Mantle.func.w(20), 20 будет менять в меньшую сторону или большую в зависимости от ширины экрана
]]--
function Mantle.func.w(px)
if !Mantle.func.w_save[px] then
Mantle.func.w_save[px] = px / 1920 * Mantle.func.sw
end
return Mantle.func.w_save[px]
end
--[[
Получение относительной высоты (на основе 1080)
]]--
function Mantle.func.h(px)
if !Mantle.func.h_save[px] then
Mantle.func.h_save[px] = px / 1080 * Mantle.func.sh
end
return Mantle.func.h_save[px]
end
local function EntText(text, y)
surface.SetFont('Fated.40')
local tw, th = surface.GetTextSize(text)
local bx, by = -tw * 0.5 - 18, y - 12
local bw, bh = tw + 36, th + 24
RNDX().Rect(bx, by, bw, bh - 6)
:Radii(16, 16, 0, 0)
:Blur()
:Shape(RNDX.SHAPE_IOS)
:Draw()
RNDX().Rect(bx, by, bw, bh - 6)
:Radii(16, 16, 0, 0)
:Color(Mantle.color.background_alpha)
:Shape(RNDX.SHAPE_IOS)
:Draw()
RNDX().Rect(bx, by + bh - 6, bw, 6)
:Radii(0, 0, 16, 16)
:Color(Mantle.color.text)
:Draw()
draw.SimpleText(text, 'Fated.40', 0, y - 2, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
end
--[[
Отрисовка текста на Entity.
Применяется в функциях отрисовки (например ENT:Draw)
]]--
function Mantle.func.draw_ent_text(ent, text, posY)
local distSqr = EyePos():DistToSqr(ent:GetPos())
local maxDist = 380
if distSqr > maxDist * maxDist then return end
local dist = math.sqrt(distSqr)
local minDist = 20
local idx = ent:EntIndex()
local prev = Mantle.func.ents_scales[idx] or 0
local normalized = math.Clamp((maxDist - dist) / math.max(1, (maxDist - minDist)), 0, 1)
local appearThreshold = 0.8
local disappearThreshold = 0.01
local target
if normalized <= disappearThreshold then
target = 0
elseif normalized >= appearThreshold then
target = 1
else
target = (normalized - disappearThreshold) / (appearThreshold - disappearThreshold)
end
local dt = FrameTime() or 0.016
local appearSpeed = 18
local disappearSpeed = 12
local speed = (target > prev) and appearSpeed or disappearSpeed
local cur = Mantle.func.approachExp(prev, target, speed, dt)
if math.abs(cur - target) < 0.0005 then cur = target end
Mantle.func.ents_scales[idx] = cur
local eased = Mantle.func.easeInOutCubic(cur)
local alpha = eased
local baseScale = 0.13
local camScale = baseScale * math.max(1e-4, eased)
if eased < 0.01 then
surface.SetAlphaMultiplier(1)
return
end
local _, max = ent:GetRotatedAABB(ent:OBBMins(), ent:OBBMaxs())
local rot = (ent:GetPos() - EyePos()):Angle().yaw - 90
local bob = math.sin(CurTime() + idx) / 3 + 0.5
local center = ent:LocalToWorld(ent:OBBCenter())
surface.SetAlphaMultiplier(alpha)
cam.Start3D2D(center + Vector(0, 0, math.abs(max.z / 2) + 12 + bob), Angle(0, rot, 90), camScale)
EntText(text, posY)
cam.End3D2D()
surface.SetAlphaMultiplier(1)
end
local scaleFactor = 0.8
function Mantle.func.animate_appearance(panel, target_w, target_h, duration, alpha_dur, callback, scale_factor)
if not IsValid(panel) then return end
duration = (duration and duration > 0) and duration or 0.18
alpha_dur = (alpha_dur and alpha_dur > 0) and alpha_dur or duration
local startTime = SysTime()
local targetX, targetY = panel:GetPos()
local initialW = target_w * (scale_factor and scale_factor or scaleFactor)
local initialH = target_h * (scale_factor and scale_factor or scaleFactor)
local initialX = targetX + (target_w - initialW) / 2
local initialY = targetY + (target_h - initialH) / 2
panel:SetSize(initialW, initialH)
panel:SetPos(initialX, initialY)
panel:SetAlpha(0)
local curW, curH = initialW, initialH
local curX, curY = initialX, initialY
local curA = 0
local eps = 0.5
local alpha_eps = 1
local speedSize = 3 / math.max(0.0001, duration)
local speedAlpha = 3 / math.max(0.0001, alpha_dur)
panel.Think = function()
if not IsValid(panel) then return end
local dt = FrameTime()
curW = Mantle.func.approachExp(curW, target_w, speedSize, dt)
curH = Mantle.func.approachExp(curH, target_h, speedSize, dt)
curX = Mantle.func.approachExp(curX, targetX, speedSize, dt)
curY = Mantle.func.approachExp(curY, targetY, speedSize, dt)
curA = Mantle.func.approachExp(curA, 255, speedAlpha, dt)
panel:SetSize(curW, curH)
panel:SetPos(curX, curY)
panel:SetAlpha(math.floor(curA + 0.5))
local doneSize = math.abs(curW - target_w) <= eps and math.abs(curH - target_h) <= eps
local donePos = math.abs(curX - targetX) <= eps and math.abs(curY - targetY) <= eps
local doneAlpha = math.abs(curA - 255) <= alpha_eps
if doneSize and donePos and doneAlpha then
panel:SetSize(target_w, target_h)
panel:SetPos(targetX, targetY)
panel:SetAlpha(255)
panel.Think = nil
if callback then callback(panel) end
end
end
end
--[[
Плавное изменение цвета с одного на другой
]]--
function Mantle.func.LerpColor(frac, col1, col2)
local ft = FrameTime() * frac
return Color(
Lerp(ft, col1.r, col2.r),
Lerp(ft, col1.g, col2.g),
Lerp(ft, col1.b, col2.b),
Lerp(ft, col1.a, col2.a)
)
end
--[[
Функции анимации
]]--
function Mantle.func.approachExp(current, target, speed, dt)
local t = 1 - math.exp(-speed * dt)
return current + (target - current) * t
end
function Mantle.func.easeOutCubic(t)
return 1 - (1 - t) * (1 - t) * (1 - t)
end
function Mantle.func.easeInOutCubic(t)
if t < 0.5 then
return 4 * t * t * t
else
return 1 - math.pow(-2 * t + 2, 3) / 2
end
end
--[[
Умное позиционирование панели относительно экрана
]]--
function Mantle.func.ClampMenuPosition(panel)
if not IsValid(panel) then return end
local x, y = panel:GetPos()
local w, h = panel:GetSize()
local sw, sh = Mantle.func.sw, Mantle.func.sh
if x < 5 then x = 5 elseif x + w > sw - 5 then x = sw - 5 - w end
if y < 5 then y = 5 elseif y + h > sh - 5 then y = sh - 5 - h end
panel:SetPos(x, y)
end
end
CreateFunc()
CreateFonts()
hook.Add('OnScreenSizeChanged', 'Mantle', function()
local newW, newH = ScrW(), ScrH()
if newW != Mantle.func.sw and newH != Mantle.func.sh then
Mantle.func.sw, Mantle.func.sh = newW, newH
Mantle.func.w_save = {}
Mantle.func.h_save = {}
CreateFunc()
-- CreateFonts()
end
end)

View File

@@ -0,0 +1,25 @@
function Mantle.lang.get(addon, key)
if !Mantle.lang.list[addon] then
print('Mantle.lang.get: addon "' .. addon .. '" not found!')
end
local lang = GetConVar('gmod_language'):GetString()
local langTable = Mantle.lang.list[addon][lang]
if !Mantle.lang.list[addon][lang] then
langTable = Mantle.lang.list[addon][Mantle.lang.default]
end
if !langTable then
for _, v in pairs(Mantle.lang.list[addon]) do
langTable = v
break
end
if !langTable then
print('Mantle.lang.get: addon "' .. addon .. '" has no language tables!')
return key
end
end
return langTable[key] or key
end

View File

@@ -0,0 +1,314 @@
--[[
Старые функции отрисовки ui-элементов
]]--
local color_gray = Color(200, 200, 200)
local color_red = Color(255, 50, 50)
local mat_close = Material('mantle/close_btn.png')
function Mantle.ui.frame(s, title, width, height, close_bool, anim_bool)
s:SetSize(width, height)
s:SetTitle('')
s:ShowCloseButton(false)
s:DockPadding(6, 30, 6, 6)
s.f_title = title
s.center_title = ''
s.background_alpha = true
s.Paint = function(self, w, h)
local x, y = self:LocalToScreen()
BShadows.BeginShadow()
draw.RoundedBoxEx(6, x, y, w, 24, Mantle.color.header, true, true)
draw.RoundedBoxEx(6, x, y + 24, w, h - 24, s.background_alpha and Mantle.color.background_alpha or Mantle.color.background, false, false, true, true)
draw.SimpleText(self.f_title, 'Fated.16', x + 6, y + 4, Mantle.color.text)
if self.center_title then
draw.SimpleText(s.center_title, 'Fated.20b', x + w * 0.5, y + 11, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
BShadows.EndShadow(1, 2, 2, 255, 0, 0)
end
if anim_bool then
Mantle.func.animate_appearance(s, width, height, 0.1, 0.2)
end
if close_bool then
s.cls = vgui.Create('DButton', s)
s.cls:SetSize(20, 20)
s.cls:SetPos(width - 22, 2)
s.cls:SetText('')
s.cls.Paint = function(_, w, h)
surface.SetDrawColor(color_white)
surface.SetMaterial(mat_close)
surface.DrawTexturedRect(0, 0, w, h)
end
s.cls.DoClick = function()
s:AlphaTo(0, 0.1, 0, function()
s:Remove()
end)
end
s.cls.DoRightClick = function()
local DM = Mantle.ui.derma_menu()
DM:AddOption('Закрыть окно', function()
s:Remove()
end, 'icon16/cross.png')
end
end
end
function Mantle.ui.sp(s)
local vbar = s:GetVBar()
vbar:SetWide(12)
vbar:SetHideButtons(true)
vbar.Paint = nil
vbar.btnGrip.Paint = function(self, w, h)
if self.Depressed then
self:SetCursor('sizens')
end
draw.RoundedBox(6, 6, 0, w - 6, h, Mantle.color.theme)
end
end
function Mantle.ui.btn(s, icon, icon_size, col, rad, off_grad_bool, hov_color, off_hov_bool)
s:SetTall(32)
s.hoverStatus = 0
s.btn_font = 'Fated.18'
s.Paint = function(self, w, h)
if !self.btn_text then
self.btn_text = self:GetText()
self:SetText('')
end
if self:IsHovered() then
self.hoverStatus = math.Clamp(self.hoverStatus + 4 * FrameTime(), 0, 255)
else
self.hoverStatus = math.Clamp(self.hoverStatus - 8 * FrameTime(), 0, 255)
end
draw.RoundedBox(rad and rad or 6, 0, 0, w, h, col and col or Mantle.color.button)
if !off_hov_bool then
local color_hover = hov_color and hov_color or Mantle.color.button_hovered
color_hover = Color(color_hover.r, color_hover.g, color_hover.b, 255 * self.hoverStatus)
draw.RoundedBox(rad and rad or 6, 0, 0, w, h, color_hover)
end
if !off_grad_bool then
Mantle.func.gradient(0, 0, w, h, 1, Mantle.color.button_shadow)
end
draw.SimpleText(self.btn_text, self.btn_font, w * 0.5 + (icon and icon_size * 0.5 - 2 or 0), h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if icon then
surface.SetDrawColor(color_white)
surface.SetMaterial(icon)
local indent = (h - icon_size) * 0.5
surface.DrawTexturedRect(indent, indent, icon_size, icon_size)
end
end
end
function Mantle.ui.slidebox(parent, label, min_value, max_value, convar, decimals)
local slider = vgui.Create('DButton', parent)
slider:Dock(TOP)
slider:DockMargin(0, 6, 0, 0)
slider:SetTall(40)
slider:SetText('')
local value = GetConVar(convar):GetFloat()
local sections = max_value - min_value
local smoothPos = 0
local targetPos = 0
local function UpdateSliderPosition(new_value)
local progress = (new_value - min_value) / sections
targetPos = (slider:GetWide() - 16) * progress
LocalPlayer():ConCommand(convar .. ' ' .. new_value)
value = new_value
end
UpdateSliderPosition(value)
slider.Paint = function(self, w, h)
draw.RoundedBox(4, 0, h - 16, w, 6, Mantle.color.panel_alpha[1])
smoothPos = Lerp(FrameTime() * 10, smoothPos, targetPos)
draw.RoundedBox(16, smoothPos, 18, 16, 16, Mantle.color.theme)
draw.SimpleText(label, 'Fated.18', 4, 0, Mantle.color.text)
draw.SimpleText(math.Round(value, decimals), 'Fated.18', w - 4, 0, Mantle.color.text, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP)
end
local function UpdateSliderByCursorPos(x)
local progress = math.Clamp(x / (slider:GetWide() - 16), 0, 1)
local new_value = math.Round(min_value + (progress * sections), decimals)
UpdateSliderPosition(new_value)
end
slider.OnMousePressed = function(_, mcode)
if mcode == MOUSE_LEFT then
UpdateSliderByCursorPos(slider:CursorPos())
slider:MouseCapture(true)
end
end
slider.OnMouseReleased = function(_, mcode)
if mcode == MOUSE_LEFT then
slider:MouseCapture(false)
end
end
slider.OnCursorMoved = function(_, x, _)
if input.IsMouseDown(MOUSE_LEFT) then
UpdateSliderByCursorPos(x)
end
end
return slider
end
function Mantle.ui.desc_entry(parent, title, placeholder, off_title_bool)
if !off_title_bool and title then
local label = vgui.Create('DLabel', parent)
label:Dock(TOP)
label:DockMargin(4, 0, 4, 0)
label:SetText(title)
label:SetFont('Fated.16')
end
local entry_background = vgui.Create('DPanel', parent)
entry_background:Dock(TOP)
entry_background:DockMargin(4, 4, 4, 0)
entry_background:SetTall(24)
local entry = vgui.Create('DTextEntry', entry_background)
entry:Dock(FILL)
entry:DockMargin(2, 4, 2, 4)
entry:SetPlaceholderText(placeholder)
entry:SetFont('Fated.16')
entry:SetDrawLanguageID(false)
entry:SetPaintBackground(false)
return entry, entry_background
end
function Mantle.ui.checkbox(parent, text, convar)
local panel = vgui.Create('DPanel', parent)
panel:Dock(TOP)
panel:DockMargin(4, 0, 4, 0)
panel:SetTall(28)
panel.Paint = function(_, w, h)
draw.RoundedBox(6, 0, 0, w, h, Mantle.color.panel_alpha[2])
draw.SimpleText(text, 'Fated.18', 8, h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
local option = vgui.Create('DButton', panel)
option:Dock(RIGHT)
option:SetWide(56)
option:SetText('')
option.enabled = convar and GetConVar(convar):GetBool() or false
option.Paint = function(self, w, h)
draw.RoundedBoxEx(6, 0, 0, w, h, Mantle.color.panel_alpha[1], false, true, false, true)
draw.SimpleText(self.enabled and 'ВКЛ' or 'ВЫКЛ', 'Fated.19', w * 0.5 - 1, h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
option.DoClick = function()
if convar then
RunConsoleCommand(convar, option.enabled and 0 or 1)
end
option.enabled = !option.enabled
end
return panel, option
end
function Mantle.ui.panel_tabs(parent)
local panel_tabs = vgui.Create('DPanel', parent)
panel_tabs:Dock(FILL)
panel_tabs.Paint = nil
panel_tabs.content = {}
panel_tabs.active_tab = ''
panel_tabs.sp = vgui.Create('DHorizontalScroller', panel_tabs)
panel_tabs.sp:Dock(TOP)
panel_tabs.sp:DockMargin(0, 0, 0, 6)
panel_tabs.sp:SetTall(24)
panel_tabs.sp:SetOverlap(-6)
panel_tabs.panel_content = vgui.Create('DPanel', panel_tabs)
panel_tabs.panel_content:Dock(FILL)
panel_tabs.panel_content.Paint = function(_, w, h)
if panel_tabs.active_tab == '' then
draw.SimpleText('Выберете вкладку', 'Fated.16', w * 0.5, h * 0.5 - panel_tabs.sp:GetTall() - 7, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
function panel_tabs:AddTab(title, panel, icon, col, col_hov)
panel_tabs.content[title] = panel
panel_tabs.content[title]:SetParent(panel_tabs.panel_content)
panel_tabs.content[title]:Dock(FILL)
panel_tabs.content[title]:SetVisible(false)
local btn_tab = vgui.Create('DButton', panel_tabs.sp)
surface.SetFont('Fated.20')
btn_tab:SetSize(surface.GetTextSize(title) + 10 + (icon and 18 or 0), 20)
btn_tab:SetText('')
if icon then
btn_tab.icon = Material(icon)
panel_tabs.content[title].icon = icon
end
btn_tab.Paint = function(self, w, h)
draw.RoundedBox(6, 0, 0, w, h, panel_tabs.active_tab == title and (col_hov and col_hov or Mantle.color.panel[2]) or (col and col or Mantle.color.theme))
if self:IsHovered() then
draw.RoundedBox(6, 0, 0, w, h, Mantle.color.button_shadow)
end
draw.SimpleText(title, 'Fated.20', w * 0.5 + (self.icon and 9 or 0), 11, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if self.icon then
surface.SetDrawColor(color_white)
surface.SetMaterial(self.icon)
surface.DrawTexturedRect(4, 4, 16, 16)
end
end
btn_tab.DoClick = function()
panel_tabs:ActiveTab(title)
end
btn_tab.DoRightClick = function()
local DM = Mantle.ui.derma_menu()
for tab_name, tab in pairs(panel_tabs.content) do
DM:AddOption(tab_name, function()
panel_tabs:ActiveTab(tab_name)
end, tab.icon)
end
end
panel_tabs.sp:AddPanel(btn_tab)
end
function panel_tabs:ActiveTab(title)
if title == panel_tabs.active_tab then
return
end
for tab_title, tab in pairs(panel_tabs.content) do
if tab_title != title then
tab:SetVisible(false)
else
tab:SetVisible(true)
panel_tabs.active_tab = title
end
end
end
return panel_tabs
end

View File

@@ -0,0 +1,909 @@
local function CreateMenu()
if IsValid(menuMantle) then
menuMantle:Remove()
end
menuMantle = vgui.Create('MantleFrame')
menuMantle:SetSize(920, 640)
menuMantle:Center()
menuMantle:MakePopup()
menuMantle:SetTitle('Mantle')
menuMantle:SetCenterTitle('Основное меню библиотеки')
menuMantle:ShowAnimation()
local tabs = vgui.Create('MantleTabs', menuMantle)
tabs:Dock(FILL)
local function CreateTabHeader(title, subtitle, icon, pan)
local header = vgui.Create('Panel', pan)
header:Dock(TOP)
header:DockMargin(0, 0, 0, 8)
header:SetTall(56)
header.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(8)
:Color(Mantle.color.panel_alpha[2])
:Draw()
RNDX().Rect(12, h * 0.5 - 12, 24, 24)
:Color(255, 255, 255)
:Material(icon)
:Draw()
draw.SimpleText(title, 'Fated.20', 48, 10, Mantle.color.text)
draw.SimpleText(subtitle, 'Fated.16', 48, h - 10, Mantle.color.gray, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM)
end
return header
end
local function CreateCopyButton(parent, snippet)
local b = vgui.Create('DButton', parent)
b:SetText('')
b:SetWide(110)
b.Paint = function(me, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(6)
:Color(Mantle.color.panel_alpha[1])
:Draw()
draw.SimpleText('Скопировать', 'Fated.16', w / 2, h / 2, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
b.DoClick = function()
SetClipboardText(snippet)
menuMantle:Notify(snippet)
Mantle.func.sound()
end
return b
end
local function CreateInfo(info, pan)
local panelInfo = vgui.Create('Panel')
panelInfo:Dock(TOP)
panelInfo:DockMargin(0, 0, 0, 6)
panelInfo:SetTall(50)
panelInfo.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(6)
:Color(Mantle.color.panel_alpha[2])
:Draw()
Mantle.func.gradient(0, 0, 6, h, 3, Mantle.color.theme, 6)
draw.SimpleText(info[1], 'Fated.20', 16, 7, Mantle.color.text)
draw.SimpleText(info[2], 'Fated.16', 16, h - 7, Mantle.color.gray, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM)
end
local copyBtn = CreateCopyButton(panelInfo, info[1])
copyBtn:Dock(RIGHT)
copyBtn:DockMargin(0, 10, 10, 10)
pan:AddItem(panelInfo)
end
local function CreateCategory(name, info_table, pan, ui_element, is_active)
local panel = vgui.Create('MantleCategory', pan)
panel:Dock(TOP)
panel:DockMargin(0, 0, 0, 6)
panel:SetText(name)
if is_active then
panel:SetActive(true)
end
for _, info in ipairs(info_table) do
CreateInfo(info, panel)
end
if ui_element then
panel:AddItem(ui_element)
end
end
local function CreateTabElements()
local panel = vgui.Create('MantleScrollPanel')
CreateTabHeader('UI Элементы', 'Демонстрация всех компонентов Mantle. Клик по элементу открывает пример.', Material('icon16/chart_pie.png'), panel)
local menuWide = menuMantle:GetWide()
-- Кнопка
local panelBtns = vgui.Create('Panel')
panelBtns:Dock(TOP)
panelBtns:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
panelBtns:SetTall(132)
local btn1 = vgui.Create('MantleBtn', panelBtns)
btn1:Dock(TOP)
btn1:SetTall(40)
btn1:SetTxt('Стандартная кнопка')
local btn2 = vgui.Create('MantleBtn', panelBtns)
btn2:Dock(TOP)
btn2:DockMargin(0, 6, 0, 0)
btn2:SetTall(40)
btn2:SetTxt('Эффект волны')
btn2:SetRipple(true)
local btn3 = vgui.Create('MantleBtn', panelBtns)
btn3:Dock(TOP)
btn3:DockMargin(0, 6, 0, 0)
btn3:SetTall(40)
btn3:SetTxt('Кастомный цвет')
btn3:SetColor(Color(182, 65, 65))
btn3:SetColorHover(Color(143, 57, 57))
btn3:SetIcon(Material('icon16/delete.png'), 16)
CreateCategory('Кнопка (MantleBtn)', {
{':SetHover(bool is_hover)', 'Включить/выключить цвет наведения (дефолт - true)'},
{':SetFont(string font)', 'Установить шрифт'},
{':SetRadius(int rad)', 'Установить размер закругления'},
{':SetIcon(string icon, int icon_size)', 'Установить иконку'},
{':SetTxt(string text)', 'Установить текст'},
{':SetColor(color col)', 'Установить цвет кнопки'},
{':SetColorHover(color col)', 'Установить цвет наведения'},
{':SetGradient(bool is_grad)', 'Включить/выключить градиент (дефолт - true)'},
{':SetRipple(bool is_ripple)', 'Включить/выключить эффект волн (дефолт - false)'}
}, panel, panelBtns)
-- Чекбокс
local checkbox = vgui.Create('MantleCheckBox')
checkbox:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
checkbox:Dock(TOP)
checkbox:SetTxt('Отображение HUD')
checkbox:SetConvar('cl_drawhud')
CreateCategory('Тумблер (MantleCheckBox)', {
{':SetTxt(string text)', 'Установить текст'},
{':SetValue(bool value)', 'Установить bool-значение тумблера'},
{':GetBool()', 'Получить bool-значение тумблера'},
{':SetConvar(string convar)', 'Установить ConVar'},
{':SetDescription(string desc)', 'Установить описание для тумблера'},
{':OnChange(bool new_value)', 'Вызывается при изменении значения тумблера'}
}, panel, checkbox)
-- Ввод текста
local entry = vgui.Create('MantleEntry')
entry:Dock(TOP)
entry:DockMargin(menuWide * 0.35, 6, menuWide * 0.35, 0)
entry:SetTitle('Никнейм')
entry:SetPlaceholder('darkf')
CreateCategory('Ввод текста (MantleEntry)', {
{':SetTitle(string text)', 'Установить заголовок'},
{':SetPlaceholder(string text)', 'Установить фоновый текст (появляется при пустом поле)'},
{':GetValue()', 'Получить string-значение поля'}
}, panel, entry)
-- Окно
local panelFrames = vgui.Create('Panel')
panelFrames:Dock(TOP)
panelFrames:SetTall(92)
local btnFrame1 = vgui.Create('MantleBtn', panelFrames)
btnFrame1:Dock(TOP)
btnFrame1:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
btnFrame1:SetTxt('Обычное окно')
btnFrame1:SetTall(40)
btnFrame1.DoClick = function()
local frame = vgui.Create('MantleFrame')
frame:SetSize(400, 300)
frame:Center()
frame:MakePopup()
frame:SetCenterTitle('Центр')
end
local btnFrame2 = vgui.Create('MantleBtn', panelFrames)
btnFrame2:Dock(TOP)
btnFrame2:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
btnFrame2:SetTxt('Lite-режим')
btnFrame2:SetTall(40)
btnFrame2.DoClick = function()
local frame = vgui.Create('MantleFrame')
frame:SetSize(400, 300)
frame:Center()
frame:MakePopup()
frame:LiteMode()
end
CreateCategory('Окно (MantleFrame)', {
{':SetAlphaBackground(bool is_alpha)', 'Включить/выключить прозрачность окна (дефолт - false)'},
{':SetTitle(string title)', 'Установить заголовок'},
{':SetCenterTitle(string title)', 'Установить центральный заголовок'},
{':ShowAnimation()', 'Активировать анимацию при появлении меню'},
{':DisableCloseBtn()', 'Скрыть кнопку закрытия'},
{':SetDraggable(bool is_draggable)', 'Включить/выключить перемещение окна'},
{':LiteMode()', 'Активировать режим Lite (без верхней панели)'},
{':Notify(string text, number duration, color col)', 'Показать уведомление внизу окна (дефолт времени - 2 сек., цвета - Mantle.color.theme)'}
}, panel, panelFrames)
-- ScrollPanel
local sp = vgui.Create('MantleScrollPanel')
sp:Dock(TOP)
sp:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
sp:SetTall(150)
for spK = 1, 10 do
local spPanel = vgui.Create('DPanel', sp)
spPanel:Dock(TOP)
spPanel:DockMargin(0, 0, 0, 6)
spPanel:SetTall(24)
spPanel.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.panel_alpha[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
end
CreateCategory('Панель прокрутки (MantleScrollPanel)', {
{':SetScroll(number offset)', 'Установить смещение прокрутки'},
{':GetScroll()', 'Получить текущее смещение прокрутки'},
{':AddItem(object panel)', 'Добавить элемент в панель'},
{':Clear()', 'Очистить панель от всего'},
{':DisableVBarPadding()', 'Отключить отступ справа для скроллбара (по умолчанию имеется)'}
}, panel, sp)
-- Вкладки
local panelTabs = vgui.Create('Panel')
panelTabs:Dock(TOP)
panelTabs:SetTall(280)
local testTabs = vgui.Create('MantleTabs', panelTabs) -- modern стиль
testTabs:Dock(TOP)
testTabs:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
testTabs:SetTall(150)
local testTab1 = vgui.Create('DPanel')
testTab1.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(53, 98, 40)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
testTabs:AddTab('Test1', testTab1)
local testTab2 = vgui.Create('DPanel')
testTab2.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(108, 41, 45)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
testTabs:AddTab('Test2', testTab2)
local testTabs2 = vgui.Create('MantleTabs', panelTabs) -- classic стиль
testTabs2:Dock(FILL)
testTabs2:DockMargin(menuWide * 0.3, 10, menuWide * 0.3, 0)
testTabs2:SetTabStyle('classic')
local testTab3 = vgui.Create('DPanel')
testTab3.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(51, 61, 116)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
testTabs2:AddTab('Test3', testTab3)
local testTab4 = vgui.Create('DPanel')
testTab4.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(138, 89, 43)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
testTabs2:AddTab('Test4', testTab4)
local testTab5 = vgui.Create('DPanel')
testTab5.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(43, 138, 133)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
testTabs2:AddTab('С иконкой', testTab5, Material('icon16/folder.png'))
CreateCategory('Вкладки (MantleTabs)', {
{':SetTabStyle(string style)', 'Установить стиль вкладок (modern или classic)'},
{':SetTabHeight(int height)', 'Установить высоту вкладок'},
{':SetIndicatorHeight(int height)', 'Установить высоту индикатора вкладок'},
{':AddTab(string name, object panel, string icon)', 'Добавить вкладку'}
}, panel, panelTabs)
-- Выбор варианта
local combo = vgui.Create('MantleComboBox')
combo:SetPlaceholder('Выберите вариант')
combo:AddChoice('Вариант 1', 'value1')
combo:AddChoice('Вариант 2', 'value2')
combo:AddChoice('Вариант 3', 'value3')
combo:AddChoice('Вариант 4', 'value4')
combo:AddChoice('Вариант 5', 'value5')
combo:AddChoice('Вариант 6', 'value6')
combo:AddChoice('Вариант 7', 'value7')
combo:AddChoice('Вариант 8', 'value8')
combo.OnSelect = function(idx, text, data)
chat.AddText(color_white, 'Вы выбрали: ', Mantle.color.theme, text, color_white, ' (', tostring(data), ')')
end
combo:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
combo:Dock(TOP)
CreateCategory('Выпадающий список (MantleComboBox)', {
{':AddChoice(string text, any data)', 'Добавить вариант в список (data — любое значение, связанное с пунктом)'},
{':SetValue(string text)', 'Установить выбранное значение по тексту'},
{':GetValue()', 'Получить выбранное значение (текст)'},
{':SetPlaceholder(string text)', 'Установить текст-заполнитель (placeholder)'},
{':OnSelect(idx, text, data)', 'Вызывается при выборе варианта: idx — индекс, text — текст, data — значение'}
}, panel, combo)
-- Таблица
local tableExample = vgui.Create('MantleTable')
tableExample:Dock(TOP)
tableExample:DockMargin(menuWide * 0.2, 6, menuWide * 0.2, 0)
tableExample:SetTall(250)
tableExample:AddColumn('Название', 200, TEXT_ALIGN_LEFT, true)
tableExample:AddColumn('Тип', 120, TEXT_ALIGN_CENTER, true)
tableExample:AddColumn('Качество', 100, TEXT_ALIGN_CENTER, true)
tableExample:AddColumn('Цена', 110, TEXT_ALIGN_RIGHT, true)
local products = {
{'Молоко "Домик в деревне"', 'Молочка', 'Высшее', 89},
{'Хлеб "Бородинский"', 'Выпечка', 'Стандарт', 45},
{'Сок "Добрый"', 'Напитки', 'Премиум', 120},
{'Шоколад "Аленка"', 'Конфеты', 'Высшее', 95},
{'Йогурт "Активиа"', 'Молочка', 'Премиум', 65},
{'Пельмени "Сибирские"', 'Заморозка', 'Стандарт', 350},
{'Колбаса "Докторская"', 'Мясо', 'Высшее', 450},
{'Сыр "Российский"', 'Молочка', 'Стандарт', 380},
{'Пицца "Пепперони"', 'Заморозка', 'Премиум', 450},
{'Чай "Липтон"', 'Напитки', 'Стандарт', 180},
{'Печенье "Юбилейное"', 'Выпечка', 'Стандарт', 85},
{'Масло "Крестьянское"', 'Молочка', 'Высшее', 120},
{'Сметана "Простоквашино"', 'Молочка', 'Стандарт', 65},
{'Курица "Бройлер"', 'Мясо', 'Стандарт', 280},
{'Рыба "Минтай"', 'Морепродукты', 'Стандарт', 320},
{'Яблоки "Голден"', 'Фрукты', 'Высшее', 180},
{'Картофель', 'Овощи', 'Стандарт', 45},
{'Морковь', 'Овощи', 'Стандарт', 35},
{'Бананы', 'Фрукты', 'Стандарт', 120},
{'Апельсины', 'Фрукты', 'Премиум', 180}
}
for _, product in ipairs(products) do
tableExample:AddItem(unpack(product))
end
tableExample:SetAction(function(row_data)
chat.AddText(color_white, 'Выбран продукт: ', Mantle.color.theme, row_data[1], color_white, ' (', row_data[2], ')')
end)
CreateCategory('Таблица (MantleTable)', {
{':AddColumn(string name, number width, number align, bool sortable)', 'Добавить колонку'},
{':AddItem(...)', 'Добавить строку. Количество аргументов должно соответствовать количеству колонок'},
{':SetAction(function(table row_data))', 'Установить функцию, вызываемую при клике на строку. row_data — массив значений строки'},
{':SetRightClickAction(function(table row_data))', 'Установить функцию, вызываемую при правом клике на строку'},
{':Clear()', 'Очистить таблицу от всех строк'},
{':GetSelectedRow()', 'Получить данные выбранной строки (массив значений)'},
{':GetRowCount()', 'Получить количество строк в таблице'},
{':RemoveRow(number index)', 'Удалить строку по индексу (начиная с 1)'}
}, panel, tableExample)
-- Категория
local panelCat = vgui.Create('Panel')
panelCat:Dock(TOP)
panelCat:DockMargin(0, 6, 0, 0)
panelCat:SetTall(142)
panelCat.Paint = nil
local cat = vgui.Create('MantleCategory', panelCat)
cat:Dock(TOP)
cat:SetCenterText(true)
cat:SetActive(true)
local panGreen = vgui.Create('DPanel')
panGreen:Dock(TOP)
panGreen:SetTall(50)
panGreen.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(93, 179, 101)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
cat:AddItem(panGreen)
local panRed = vgui.Create('DPanel')
panRed:Dock(TOP)
panRed:DockMargin(0, 6, 0, 0)
panRed:SetTall(50)
panRed.Paint = function(_, w, h)
RNDX().Rect(0, 0, w - 12, h)
:Rad(16)
:Color(179, 110, 93)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
cat:AddItem(panRed)
CreateCategory('Категория (MantleCategory)', {
{':SetText(string name)', 'Установить название'},
{':AddItem(object panel)', 'Добавить в категорию элемент'},
{':SetColor(color col)', 'Установить кастомный цвет категории'},
{':SetCenterText(bool is_centered)', 'Установить центрирование названия'},
{':SetActive(bool is_active)', 'Установить активность категории (дефолт - false)'}
}, panel, panelCat)
-- Слайдер
local slider = vgui.Create('MantleSlideBox')
slider:Dock(TOP)
slider:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
slider:SetRange(0, 4)
slider:SetConvar('net_graph')
slider:SetText('График')
CreateCategory('Слайдер (MantleSlideBox)', {
{':SetRange(int min_value, int max_value, int decimals)', 'Сделать диапазон слайдера с точностью (дефолт точность - 0)'},
{':SetConvar(string convar)', 'Установить ConVar'},
{':SetText(string text)', 'Установить текстовое обозначение'},
{':SetValue(string val)', 'Установить значение'},
{':GetValue()', 'Получить выбранное значение (число)'},
{':OnValueChanged(string new_value)', 'Вызывается при изменении значения слайдера'}
}, panel, slider)
local panelTexts = vgui.Create('Panel')
panelTexts:Dock(TOP)
panelTexts:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
panelTexts:DockPadding(8, 8, 8, 8)
panelTexts:SetTall(344)
panelTexts.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.panel_alpha[2])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local panelText1 = vgui.Create('DPanel', panelTexts)
panelText1:Dock(TOP)
panelText1:DockMargin(0, 0, 0, 6)
panelText1:SetTall(74)
panelText1.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.panel_alpha[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local text1 = vgui.Create('MantleText', panelText1)
text1:Dock(FILL)
text1:SetPadding(10)
text1:SetText('MantleText — компонент для аккуратного вывода многострочного текста. Текст автоматически переносится по ширине и сокращается троеточием')
local panelText2 = vgui.Create('DPanel', panelTexts)
panelText2:Dock(TOP)
panelText2:DockMargin(0, 0, 0, 6)
panelText2:SetTall(100)
panelText2.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.panel_alpha[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local text2 = vgui.Create('MantleText', panelText2)
text2:Dock(FILL)
text2:SetPadding(12)
text2:SetFont('Fated.20')
text2:SetText('Центрирование: горизонталь + вертикаль. Текст выровнен по центру блока.')
text2:SetAlign(TEXT_ALIGN_CENTER)
text2:SetVAlign('center')
local panelText3 = vgui.Create('DPanel', panelTexts)
panelText3:Dock(TOP)
panelText3:DockMargin(0, 0, 0, 6)
panelText3:SetTall(54)
panelText3.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.panel_alpha[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local text3 = vgui.Create('MantleText', panelText3)
text3:Dock(FILL)
text3:SetPadding(8)
text3:SetText('ОченьДлинноеСловоБезПробеловКотороеНужноОтделитьЧтобыНеПорвалосьОформление')
local panelText4 = vgui.Create('DPanel', panelTexts)
panelText4:Dock(TOP)
panelText4:SetTall(82)
panelText4.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.panel_alpha[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local longText = [[
Это длинный пример текста, который занимает несколько строк. Если блок небольшой по высоте — последняя видимая строка будет усечена с троеточием, чтобы не порвать верстку и не выходить за пределы панели нашего меню.
]]
local text4 = vgui.Create('MantleText', panelText4)
text4:Dock(FILL)
text4:SetPadding(8)
text4:SetFont('Fated.16')
text4:SetText(longText)
CreateCategory('Текст (MantleText)', {
{':SetText(string text)', 'Установить текст для отображения'},
{':SetFont(string font)', 'Установить шрифт'},
{':SetColor(color col)', 'Установить цвет текста'},
{':SetAlign(number align)', 'Горизонтальное выравнивание (TEXT_ALIGN_*)'},
{':SetVAlign(string valign)', 'Вертикальное выравнивание: top, center, bottom'},
{':SetPadding(number px)', 'Внутренний отступ от краёв'}
}, panel, panelTexts)
return panel
end
tabs:AddTab('UI Элементы', CreateTabElements(), Material('icon16/chart_pie.png'))
local function CreateShowMenus()
local panel = vgui.Create('MantleScrollPanel')
CreateTabHeader('Всплывающие', 'Палитра, derma-меню, radial и другие утилиты.', Material('icon16/application_double.png'), panel)
local listMenus = {
{'Выбор цвета через палитру', function()
Mantle.ui.color_picker(function(col)
chat.AddText('Вы выбрали цвет: ', col, tostring(col))
end, Color(25, 59, 102))
end},
{'Опциональное меню (Derma Menu)', function()
local DM = Mantle.ui.derma_menu()
for i = 1, 5 do
DM:AddOption('Опция ' .. i, function()
chat.AddText('Привет всем! ' .. i)
end)
end
DM:AddSpacer()
DM:AddOption('Узнать свою привилегию', function()
chat.AddText(LocalPlayer():GetUserGroup())
end, 'icon16/status_online.png')
end},
{'Опциональное с подменю (Derma Menu)', function()
local DM = Mantle.ui.derma_menu()
local clothes = DM:AddOption('Одежда')
local subClothes = clothes:AddSubMenu()
subClothes:AddOption('Шапка', function()
chat.AddText('Вы выбрали: Шапка')
end)
subClothes:AddOption('Свитер', function()
chat.AddText('Вы выбрали: Свитер')
end)
local food = DM:AddOption('Еда')
local subFood = food:AddSubMenu()
subFood:AddOption('Морковь', function()
chat.AddText('Вы выбрали: Морковь')
end)
subFood:AddOption('Яблоко', function()
chat.AddText('Вы выбрали: Яблоко')
end)
end},
{'Выбор игрока', function()
Mantle.ui.player_selector(function(pl)
chat.AddText('Вы выбрали игрока: ', color_white, pl:Name())
end)
end},
{'Круговое меню', function()
--[[
Имеется возможность настроить радиальное меню
local configRadial = {
disable_background = true, -- отключает фон
hover_sound = 'buttons/button14.wav', -- звук при наведении
scale_animation = false, -- отключает анимацию масштабирования
radius = 300, -- радиус меню
inner_radius = 100 -- радиус внутреннего круга
}
local rm = Mantle.ui.radial_menu(configRadial)
--]]
local rm = Mantle.ui.radial_menu()
rm:SetCenterText('Действия', 'Выберите действие')
local weaponsMenu = rm:CreateSubMenu('Оружие', 'Выберите оружие')
weaponsMenu:AddOption('Пистолет', function()
chat.AddText(Mantle.color.theme, 'Выбран пистолет')
end, 'icon16/gun.png', 'Обычный пистолет')
weaponsMenu:AddOption('Винтовка', function()
chat.AddText(Mantle.color.theme, 'Выбрана винтовка')
end, 'icon16/gun.png', 'Мощная винтовка')
rm:AddSubMenuOption('Оружие', weaponsMenu, 'icon16/gun.png', 'Выберите оружие')
-- Обычные опции
rm:AddOption('Выбросить', function()
chat.AddText('Выбросить оружие')
end, 'icon16/gun.png', 'Выбросить оружие')
rm:AddOption('Кинуть кубик', function()
chat.AddText('Действие выполнено')
end, 'icon16/controller.png', 'Рандом кубика')
rm:AddOption('Погибнуть', function()
chat.AddText('Действие выполнено')
end, 'icon16/world.png', 'Попрощаться с миром')
rm:AddOption('Хакнуть', function()
chat.AddText('Действие выполнено')
end, 'icon16/server.png', 'Взломать сервер')
rm:AddOption('Посмотреть баланс', function()
chat.AddText('Действие выполнено')
end, 'icon16/money.png', 'Сколько у вас денег')
rm:AddOption('Нет иконки', function()
chat.AddText('Действие выполнено')
end, nil, 'Где иконка?')
end},
{'Написание текста', function()
Mantle.ui.text_box('Заголовок', 'Описание того, что вводиться', function(s)
chat.AddText('Вы ввели: ', color_white, s)
end)
end},
{'Вызов сообщения в Окне', function()
menuMantle:Notify('Тестовое сообщение!')
end}
}
for _, elem in ipairs(listMenus) do
local btn = vgui.Create('MantleBtn', panel)
btn:Dock(TOP)
btn:DockMargin(0, 0, 0, 6)
btn:SetTall(30)
btn:SetTxt(elem[1])
btn.DoClick = function()
elem[2]()
Mantle.func.sound()
end
end
return panel
end
tabs:AddTab('Всплывающие', CreateShowMenus(), Material('icon16/application_double.png'))
local function CreateTabFunctions()
local panel = vgui.Create('MantleScrollPanel')
CreateTabHeader('Функции', 'Полный список утилитарных функций Mantle.func и других вспомогательных функций', Material('icon16/cog.png'), panel)
local menuWide = menuMantle:GetWide()
CreateCategory('Размытие панели', {
{'Mantle.func.blur(object panel)', 'Отрисовка размытия панели в Paint'}
}, panel)
CreateCategory('Градиент', {
{'Mantle.func.gradient(int x, int y, int w, int h, int dir, color color_shadow, int radius, flags)', 'Отрисовка градиента (dir: 1 - вверх, 2 - вниз, 3 - влево, 4 - вправо)'}
}, panel)
CreateCategory('Создание звука', {
{'Mantle.func.sound(string path)', 'Проигрывает звук (дефолт - mantle/btn_click.ogg)'}
}, panel)
CreateCategory('Относительные единицы для адаптивного интерфейса', {
{'Mantle.func.w(int px)', 'Относительная ширина (от 1920)'},
{'Mantle.func.h(int px)', 'Относительная высота (от 1080)'}
}, panel)
CreateCategory('Отрисовка текста над энтити', {
{'Mantle.func.draw_ent_text(object ent, string text, int posY)', 'Рисует текст над энтити с плавным появлением (3D2D)'}
}, panel)
CreateCategory('Анимация размера панели', {
{'Mantle.func.animate_appearance(object panel, int w, int h, int duration, int alpha_dur, func callback, int scale_factor)', 'Плавное изменение панели до нужного размера'}
}, panel)
CreateCategory('Плавное изменение цвета', {
{'Mantle.func.LerpColor(int frac, color col1, color col2)', 'Плавный переход цвета от col1 → col2'}
}, panel)
CreateCategory('Загрузка картинки', {
{'http.DownloadMaterial(string url, string path, func callback, int retry_count)', 'Скачивает материал по URL и кэширует его. Повторяет попытку при ошибке, возвращает через callback материал'}
}, panel)
CreateCategory('Серверное уведомление', {
{'Mantle.notify(object pl, color header_color, string header, string text)', 'Отправка сообщений в чат игроку или всем (вместо pl указать true - тогда всем)'}
}, panel)
CreateCategory('Изменение регистра букв', {
{'utf8.lower(string text)', 'Преобразует строку в нижний регистр с поддержкой русских букв'},
{'utf8.upper(string text)', 'Преобразует строку в верхний регистр с поддержкой русских букв'}
}, panel)
return panel
end
tabs:AddTab('Функции', CreateTabFunctions(), Material('icon16/error.png'))
local function CreateLegacyTest()
local panel = vgui.Create('MantleScrollPanel')
CreateTabHeader('Legacy UI', 'Набор legacy-утилит (Mantle.ui.*). Для совместимости и примеров.', Material('icon16/exclamation.png'), panel)
local menuWide = menuMantle:GetWide()
local btnFrame = vgui.Create('MantleBtn')
btnFrame:SetTxt('Открыть Legacy Frame')
btnFrame:SetTall(40)
btnFrame:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0)
btnFrame:Dock(TOP)
btnFrame.DoClick = function()
local frame = vgui.Create('DFrame')
frame:SetSize(400, 300)
frame:Center()
frame:MakePopup()
Mantle.ui.frame(frame, 'Legacy Frame', 400, 300, true, true)
local scroll = vgui.Create('DScrollPanel', frame)
scroll:Dock(FILL)
Mantle.ui.sp(scroll)
-- Тест кнопок с разными параметрами
local btn1 = vgui.Create('DButton', scroll)
btn1:Dock(TOP)
btn1:DockMargin(10, 10, 10, 0)
btn1:SetText('Обычная кнопка')
Mantle.ui.btn(btn1)
local btn2 = vgui.Create('DButton', scroll)
btn2:Dock(TOP)
btn2:DockMargin(10, 10, 10, 0)
btn2:SetText('Кнопка с иконкой')
Mantle.ui.btn(btn2, Material('icon16/accept.png'), 16)
local btn3 = vgui.Create('DButton', scroll)
btn3:Dock(TOP)
btn3:DockMargin(10, 10, 10, 0)
btn3:SetText('Кнопка без градиента')
Mantle.ui.btn(btn3, nil, nil, nil, nil, true)
local btn4 = vgui.Create('DButton', scroll)
btn4:Dock(TOP)
btn4:DockMargin(10, 10, 10, 0)
btn4:SetText('Кнопка без ховера')
Mantle.ui.btn(btn4, nil, nil, nil, nil, nil, nil, true)
-- Тест слайдеров
local slider1 = Mantle.ui.slidebox(scroll, 'Слайдер (0-100)', 0, 100, 'net_graph', 0)
slider1:DockMargin(10, 20, 10, 0)
local slider2 = Mantle.ui.slidebox(scroll, 'Слайдер (0-1)', 0, 1, 'cl_drawhud', 2)
slider2:DockMargin(10, 20, 10, 0)
-- Тест полей ввода
local entry1, entry_bg1 = Mantle.ui.desc_entry(scroll, 'Поле с заголовком', 'Введите текст...')
entry_bg1:DockMargin(10, 20, 10, 0)
local entry2, entry_bg2 = Mantle.ui.desc_entry(scroll, nil, 'Поле без заголовка')
entry_bg2:DockMargin(10, 20, 10, 0)
-- Тест чекбоксов
local checkbox1, checkbox_btn1 = Mantle.ui.checkbox(scroll, 'Чекбокс с ConVar', 'cl_drawhud')
checkbox1:DockMargin(10, 20, 10, 0)
local checkbox2, checkbox_btn2 = Mantle.ui.checkbox(scroll, 'Чекбокс без ConVar')
checkbox2:DockMargin(10, 20, 10, 0)
-- Тест вкладок
local panelTabs = vgui.Create('DPanel', scroll)
panelTabs:Dock(TOP)
panelTabs:SetTall(250)
panelTabs.Paint = nil
local tabs = Mantle.ui.panel_tabs(panelTabs)
tabs:DockMargin(10, 20, 10, 0)
-- Добавляем вкладки с разными стилями
local tab1 = vgui.Create('DPanel')
tab1.Paint = function(_, w, h)
draw.SimpleText('Вкладка 1', 'Fated.20', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
tabs:AddTab('Вкладка 1', tab1, 'icon16/page_white.png')
local tab2 = vgui.Create('DPanel')
tab2.Paint = function(_, w, h)
draw.SimpleText('Вкладка 2', 'Fated.20', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
tabs:AddTab('Вкладка 2', tab2, 'icon16/page_white_edit.png', Color(100, 200, 100))
local tab3 = vgui.Create('DPanel')
tab3.Paint = function(_, w, h)
draw.SimpleText('Вкладка 3', 'Fated.20', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
tabs:AddTab('Вкладка 3', tab3, 'icon16/page_white_gear.png', nil, Color(200, 100, 100))
tabs:ActiveTab('Вкладка 1')
end
CreateCategory('Legacy Frame (не стоит использовать)', {
{'Mantle.ui.frame(object frame, string title, int w, int h, bool cls_btn, bool open_anim)', 'Оформление стандартное окна стилем Mantle'},
{'Mantle.ui.sp(object scroll)', 'Оформление панели прокрутки элементов'},
{'Mantle.ui.btn(object btn, mat icon, int icon_size, color col, int rad, bool off_grad, color hov, bool off_hov)', 'Оформление кнопки'},
{'Mantle.ui.slidebox(object parent, string label, int min_value, int max_value, string convar, int decimals)', 'Создание слайдера на родительном элементе'},
{'Mantle.ui.desc_entry(object parent, string title, string placeholder, bool off_title)', 'Создание поля ввода'},
{'Mantle.ui.checkbox(object parent, string text, string convar)', 'Создание чекбокса'},
{'Mantle.ui.panel_tabs(object parent)', 'Создание панели с вкладками. В дальнейшем использовать :AddTab() и :ActiveTab() для настройки'}
}, panel, btnFrame, true)
return panel
end
tabs:AddTab('Legacy UI', CreateLegacyTest(), Material('icon16/exclamation.png'))
local function CreateSettings()
local panel = vgui.Create('MantleScrollPanel')
CreateTabHeader('Настройки', 'Глобальные настройки Mantle: темы, эффекты и глубины элементов.', Material('icon16/cog.png'), panel)
local menuWide = menuMantle:GetWide()
local checkboxDepth = vgui.Create('MantleCheckBox', panel)
checkboxDepth:Dock(TOP)
checkboxDepth:SetTxt('Глубины элементов')
checkboxDepth:SetConvar('mantle_depth_ui')
local checkboxBlur = vgui.Create('MantleCheckBox', panel)
checkboxBlur:Dock(TOP)
checkboxBlur:DockMargin(0, 6, 0, 0)
checkboxBlur:SetTxt('Размытие фона')
checkboxBlur:SetConvar('mantle_blur')
local categoryTheme = vgui.Create('MantleCategory', panel)
categoryTheme:Dock(TOP)
categoryTheme:DockMargin(0, 6, 0, 0)
categoryTheme:SetText('Изменение цветовой темы')
categoryTheme:SetActive(true)
local comboboxTheme = vgui.Create('MantleComboBox')
comboboxTheme:Dock(TOP)
comboboxTheme:SetPlaceholder('Выберите тему интерфейса')
comboboxTheme:AddChoice('Тёмная (dark)', 'dark')
comboboxTheme:AddChoice('Тёмная монотонная (dark_mono)', 'dark_mono')
comboboxTheme:AddChoice('Светлая (light)', 'light')
comboboxTheme:AddChoice('Синяя (blue)', 'blue')
comboboxTheme:AddChoice('Красная (red)', 'red')
comboboxTheme:AddChoice('Зелёная (green)', 'green')
comboboxTheme:AddChoice('Оранжевая (orange)', 'orange')
comboboxTheme:AddChoice('Фиолетовый (purple)', 'purple')
comboboxTheme:AddChoice('Кофейная (coffee)', 'coffee')
comboboxTheme:AddChoice('Ледяная (ice)', 'ice')
comboboxTheme:AddChoice('Винная (wine)', 'wine')
comboboxTheme:AddChoice('Фиалковая (violet)', 'violet')
comboboxTheme:AddChoice('Моховая (moss)', 'moss')
comboboxTheme:AddChoice('Коралловая (coral)', 'coral')
comboboxTheme.OnSelect = function(_, _, data)
RunConsoleCommand('mantle_theme', data)
end
categoryTheme:AddItem(comboboxTheme)
local listThemeColors = vgui.Create('DIconLayout')
listThemeColors:Dock(TOP)
listThemeColors:DockMargin(6, 8, 6, 0)
listThemeColors:SetTall(164)
listThemeColors:SetSpaceX(8)
listThemeColors:SetSpaceY(8)
categoryTheme:AddItem(listThemeColors)
for colId, _ in pairs(Mantle.color) do
local panCol = vgui.Create('DPanel', listThemeColors)
panCol:SetSize(80, 80)
panCol.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color[colId])
:Shape(RNDX.SHAPE_IOS)
:Draw()
draw.SimpleText(colId, 'Fated.12', w * 0.5, h * 0.5, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
return panel
end
tabs:AddTab('Настройки', CreateSettings(), Material('icon16/cog.png'))
end
concommand.Add('mantle_menu', CreateMenu)

View File

@@ -0,0 +1,106 @@
CreateClientConVar('mantle_depth_ui', 1, true, false)
CreateClientConVar('mantle_theme', 'dark', true, false)
CreateClientConVar('mantle_blur', 1, true, false)
Mantle.ui = {
convar = {
depth_ui = GetConVar('mantle_depth_ui'):GetBool(),
theme = GetConVar('mantle_theme'):GetString(),
blur = GetConVar('mantle_blur'):GetBool()
}
}
local themeMap = {
dark = Mantle.color_dark,
dark_mono = Mantle.color_dark_mono,
graphite = Mantle.color_graphite,
light = Mantle.color_light,
blue = Mantle.color_blue,
red = Mantle.color_red,
green = Mantle.color_green,
orange = Mantle.color_orange,
purple = Mantle.color_purple,
coffee = Mantle.color_coffee,
ice = Mantle.color_ice,
wine = Mantle.color_wine,
violet = Mantle.color_violet,
moss = Mantle.color_moss,
coral = Mantle.color_coral
}
local function isColor(v)
return type(v) == 'table' and type(v.r) == 'number'
end
local transition = {
active = false,
to = nil,
progress = 0,
speed = 3,
colorBlend = 8
}
local function startThemeTransition(name)
transition.to = table.Copy(themeMap[name] or Mantle.color_dark)
transition.active = true
transition.progress = 0
if !hook.GetTable().MantleThemeTransition then
hook.Add('Think', 'MantleThemeTransition', function()
if !transition.active then return end
local dt = FrameTime()
transition.progress = Mantle.func.approachExp(transition.progress, 1, transition.speed, dt)
local eased = Mantle.func.easeOutCubic(transition.progress)
local to = transition.to
if !to then
transition.active = false
hook.Remove('Think', 'MantleThemeTransition')
return
end
for k, v in pairs(to) do
if isColor(v) then
Mantle.color[k] = Mantle.func.LerpColor(transition.colorBlend, Mantle.color[k] or v, v)
elseif type(v) == 'table' and #v > 0 then
Mantle.color[k] = Mantle.color[k] or {}
for i = 1, #v do
local vi = v[i]
if isColor(vi) then
Mantle.color[k][i] = Mantle.func.LerpColor(transition.colorBlend, (Mantle.color[k] and Mantle.color[k][i]) or vi, vi)
else
Mantle.color[k][i] = vi
end
end
end
end
if transition.progress >= 0.999 then
Mantle.color = table.Copy(transition.to)
transition.active = false
hook.Remove('Think', 'MantleThemeTransition')
end
end)
end
end
local function ApplyInitialTheme()
local theme = Mantle.ui.convar.theme
Mantle.color = table.Copy(themeMap[theme] or Mantle.color_dark)
end
ApplyInitialTheme()
cvars.AddChangeCallback('mantle_depth_ui', function(_, _, newValue)
Mantle.ui.convar.depth_ui = newValue == '1'
end)
cvars.AddChangeCallback('mantle_theme', function(_, _, newValue)
Mantle.ui.convar.theme = newValue
startThemeTransition(newValue)
end)
cvars.AddChangeCallback('mantle_blur', function(_, _, newValue)
Mantle.ui.convar.blur = newValue == '1'
end)

View File

@@ -0,0 +1,191 @@
local PANEL = {}
function PANEL:Init()
self._activeShadowTimer = 0
self._activeShadowMinTime = 0.03 -- минимальная длительность (сек)
self._activeShadowLerp = 0
self.hover_status = 0
self.bool_hover = true
self.font = 'Fated.18'
self.radius = 16
self.icon = ''
self.icon_size = 16
self.text = Mantle.lang.get('mantle', 'btn_default')
self.col = Mantle.color.button
self.col_hov = Mantle.color.button_hovered
self.bool_gradient = true
self.click_alpha = 0
self.click_x = 0
self.click_y = 0
self.ripple_speed = 4
self.enable_ripple = false
self.ripple_color = Color(255, 255, 255, 30)
--[[
TODO: тень, которая не вылезает за окно при прокрутке
]]--
-- local parent = self:GetParent()
-- local grandParent = IsValid(parent:GetParent()) and parent:GetParent() or parent
-- self.clipParent = IsValid(parent) and grandParent or nil
self:SetText('')
end
function PANEL:SetHover(is_hover)
self.bool_hover = is_hover
end
function PANEL:SetFont(font)
self.font = font
end
function PANEL:SetRadius(rad)
self.radius = rad
end
function PANEL:SetIcon(icon, icon_size)
self.icon = type(icon) == 'IMaterial' and icon or Material(icon)
self.icon_size = icon_size
end
function PANEL:SetTxt(text)
self.text = text
end
function PANEL:SetColor(col)
self.col = col
end
function PANEL:SetColorHover(col)
self.col_hov = col
end
function PANEL:SetGradient(is_grad)
self.bool_gradient = is_grad
end
function PANEL:SetRipple(enable)
self.enable_ripple = enable
end
function PANEL:OnMousePressed(mousecode)
self.BaseClass.OnMousePressed(self, mousecode)
if self.enable_ripple and mousecode == MOUSE_LEFT then
self.click_alpha = 1
self.click_x, self.click_y = self:CursorPos()
end
end
local math_clamp = math.Clamp
local btnFlags = RNDX.SHAPE_IOS
function PANEL:Paint(w, h)
if self:IsHovered() then
self.hover_status = math_clamp(self.hover_status + 4 * FrameTime(), 0, 1)
else
self.hover_status = math_clamp(self.hover_status - 8 * FrameTime(), 0, 1)
end
-- Минимальный порог длительности для активной тени
local isActive = (self:IsDown() or self.Depressed) and self.hover_status > 0.8
if isActive then
self._activeShadowTimer = SysTime() + self._activeShadowMinTime
end
local showActiveShadow = isActive or (self._activeShadowTimer > SysTime())
-- Плавная анимация дополнительной тени при зажатии
local activeTarget = showActiveShadow and 10 or 0
local activeSpeed = (activeTarget > 0) and 7 or 3 -- скорость появления/затухания
self._activeShadowLerp = Lerp(FrameTime() * activeSpeed, self._activeShadowLerp, activeTarget)
-- Обычная тень
-- if Mantle.ui.convar.depth_ui then
-- RNDX().Rect(0, 0, w, h)
-- :Rad(self.radius)
-- :Color(Mantle.color.window_shadow)
-- :Shape(RNDX.SHAPE_IOS)
-- :Shadow(5, 20)
-- :Clip(self.clipParent)
-- :Draw()
-- end
-- Дополнительная тень при зажатии
if self._activeShadowLerp > 0 and Mantle.ui.convar.depth_ui then
local col = Color(self.col_hov.r, self.col_hov.g, self.col_hov.b, math.Clamp(self.col_hov.a * 1.5, 0, 255))
RNDX().Rect(0, 0, w, h)
:Rad(self.radius)
:Color(col)
:Shape(btnFlags)
:Shadow(self._activeShadowLerp * 1.5, 24)
:Draw()
end
RNDX().Rect(0, 0, w, h)
:Rad(self.radius)
:Color(self.col)
:Shape(btnFlags)
:Draw()
if self.bool_gradient then
Mantle.func.gradient(0, 0, w, h, 1, Mantle.color.button_shadow, self.radius, btnFlags)
end
if self.bool_hover then
RNDX().Rect(0, 0, w, h)
:Rad(self.radius)
:Color(Color(self.col_hov.r, self.col_hov.g, self.col_hov.b, self.hover_status * 255))
:Shape(btnFlags)
:Draw()
end
if self.click_alpha > 0 then
self.click_alpha = math_clamp(self.click_alpha - FrameTime() * self.ripple_speed, 0, 1)
local ripple_size = (1 - self.click_alpha) * math.max(w, h) * 2
local ripple_color = Color(
self.ripple_color.r,
self.ripple_color.g,
self.ripple_color.b,
self.ripple_color.a * self.click_alpha
)
RNDX().Rect(self.click_x - ripple_size * 0.5, self.click_y - ripple_size * 0.5, ripple_size, ripple_size)
:Rad(100)
:Color(ripple_color)
:Shape(btnFlags)
:Draw()
end
if self.text != '' then
draw.SimpleText(
self.text,
self.font,
w * 0.5 + (self.icon ~= '' and self.icon_size * 0.5 + 2 or 0),
h * 0.5,
Mantle.color.text,
TEXT_ALIGN_CENTER,
TEXT_ALIGN_CENTER
)
if self.icon != '' then
surface.SetFont(self.font)
local posX = (w - surface.GetTextSize(self.text) - self.icon_size) * 0.5 - 2
local posY = (h - self.icon_size) * 0.5
RNDX().Rect(posX, posY, self.icon_size, self.icon_size)
:Material(self.icon)
:Color(color_white)
:Shape(btnFlags)
:Draw()
end
elseif self.icon != '' then
local posX = (w - self.icon_size) * 0.5
local posY = (h - self.icon_size) * 0.5
RNDX().Rect(posX, posY, self.icon_size, self.icon_size)
:Material(self.icon)
:Color(color_white)
:Shape(btnFlags)
:Draw()
end
end
vgui.Register('MantleBtn', PANEL, 'Button')

View File

@@ -0,0 +1,130 @@
local PANEL = {}
function PANEL:Init()
self:SetTall(30)
self:DockPadding(0, 36, 0, 0)
self.name = 'Категория'
self.bool_opened = false
self.bool_header_centered = false
self.content_size = 0
self.header_color = Mantle.color.category
self.header_color_standard = self.header_color
self.header_color_opened = Mantle.color.category_opened
self._childHeights = {}
self._anim = 0
self._animTarget = 0
self._animSpeed = 12
self._animEased = 0
self.header = vgui.Create('Button', self)
self.header:SetText('')
self.header.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(self.header_color)
:Shape(RNDX.SHAPE_IOS)
:Draw()
local posX = self.bool_header_centered and w * 0.5 or 8
local alignX = self.bool_header_centered and TEXT_ALIGN_CENTER or TEXT_ALIGN_LEFT
draw.SimpleText(self.name, 'Fated.20', posX, 4, Mantle.color.text, alignX)
self.header_color = Mantle.func.LerpColor(8, self.header_color, self.bool_opened and self.header_color_opened or self.header_color_standard)
end
self.header.DoClick = function()
self.bool_opened = !self.bool_opened
self._animTarget = self.bool_opened and 1 or 0
end
end
function PANEL:SetText(name)
self.name = name
end
function PANEL:SetCenterText(is_centered)
self.bool_header_centered = is_centered
end
local function getTopBottomMargin(pnl)
if !pnl.GetDockMargin then return 0, 0 end
local ok, l, t, r, b = pcall(function()
return pnl:GetDockMargin()
end)
if !ok or !l then return 0, 0 end
return t or 0, b or 0
end
function PANEL:AddItem(panel)
panel:SetParent(self)
local top, bottom = getTopBottomMargin(panel)
local contribution = (panel.GetTall and panel:GetTall() or 0) + top + bottom
self._childHeights[panel] = contribution
self.content_size = (self.content_size or 0) + contribution
if self.bool_opened then
self:SetTall(30 + self.content_size + 12)
end
local old = panel.OnSizeChanged
panel.OnSizeChanged = function(...)
if old then pcall(old, ...) end
if !IsValid(self) then return end
local nt, nb = getTopBottomMargin(panel)
local newContribution = (panel.GetTall and panel:GetTall() or 0) + nt + nb
local oldContribution = self._childHeights[panel] or 0
local delta = newContribution - oldContribution
if delta != 0 then
self._childHeights[panel] = newContribution
self.content_size = math.max(0, (self.content_size or 0) + delta)
end
end
return panel
end
function PANEL:SetColor(col)
self.header_color_standard = col
if !self.bool_opened then
self.header_color = self.header_color_standard
end
end
function PANEL:SetActive(is_active)
is_active = tobool(is_active)
if self.bool_opened == is_active then return end
self.bool_opened = is_active
self._animTarget = is_active and 1 or 0
self.header_color = is_active and self.header_color_opened or self.header_color_standard
end
function PANEL:PerformLayout(w, h)
self.header:SetSize(w, 30)
end
function PANEL:Think()
local ft = FrameTime()
self._anim = Mantle.func.approachExp(self._anim, self._animTarget, self._animSpeed, ft)
self._animEased = Mantle.func.easeOutCubic(self._anim)
local currentContentTall = (self.content_size or 0) * self._animEased
local padded = 12 * self._animEased
local totalTall = 30 + currentContentTall + padded
self:SetTall(math.max(30, math.floor(totalTall + 0.5)))
local alphaVal = math.floor(255 * self._animEased + 0.5)
for _, c in ipairs(self:GetChildren()) do
if IsValid(c) and c != self.header then
if c.SetAlpha then c:SetAlpha(alphaVal) end
end
end
end
vgui.Register('MantleCategory', PANEL, 'Panel')

View File

@@ -0,0 +1,136 @@
local PANEL = {}
function PANEL:Init()
self.text = ''
self.convar = ''
self.value = false
self:SetText('')
self:SetCursor('hand')
self:SetTall(36)
self._circle = 0
self._circleEased = 0
self._circleColor = table.Copy(Mantle.color.gray)
self.toggle = vgui.Create('Button', self)
self.toggle:Dock(RIGHT)
self.toggle:SetWide(48)
self.toggle:DockMargin(0, 0, 14, 0)
self.toggle:SetText('')
self.toggle:SetCursor('hand')
self.toggle.Paint = nil
self.toggle.DoClick = function()
if self.convar ~= '' then
LocalPlayer():ConCommand(self.convar .. ' ' .. (self.value and 0 or 1))
end
self:SetValue(not self.value)
self:OnChange(self.value)
Mantle.func.sound()
end
end
function PANEL:OnMousePressed(mcode)
if mcode == MOUSE_LEFT then
self.toggle:DoClick()
end
end
function PANEL:SetTxt(text)
self.text = text
end
function PANEL:SetValue(val)
self.value = tobool(val)
end
function PANEL:GetBool()
return self.value
end
function PANEL:SetConvar(convar)
local c = GetConVar(convar)
if c then self.value = c:GetBool() end
self.convar = convar
end
function PANEL:OnChange(new_value)
end
function PANEL:Paint(w, h)
if Mantle.ui and Mantle.ui.convar and Mantle.ui.convar.depth_ui then
RNDX().Rect(0, 0, w, h)
:Rad(12)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(6, 22)
:Draw()
end
RNDX().Rect(0, 0, w, h)
:Rad(12)
:Color(Mantle.color.focus_panel)
:Shape(RNDX.SHAPE_IOS)
:Draw()
local textX = 14
draw.SimpleText(self.text, 'Fated.18', textX, h * 0.5, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
function PANEL:PaintOver(w, h)
local tw, th = self.toggle:GetWide(), self.toggle:GetTall()
local tx, ty = self.toggle:GetPos()
local ft = FrameTime()
local target = self.value and 1 or 0
local circleSpeed = 8
self._circle = Mantle.func.approachExp(self._circle, target, circleSpeed, ft)
if math.abs(self._circle - target) < 0.001 then self._circle = target end
self._circleEased = Mantle.func.easeInOutCubic(self._circle)
local trackW = tw - 10
local trackH = 18
local trackX = tx + (tw - trackW) / 2
local trackY = ty + (th - trackH) / 2
RNDX().Rect(trackX, trackY + 1, trackW, trackH - 2)
:Rad(trackH / 2)
:Color(Mantle.color.toggle)
:Shape(RNDX.SHAPE_IOS)
:Draw()
local circleSize = 20
local pad = 0
local textMargin = 14
local x0_base = trackX + pad - (circleSize * 0.5) + 0.5
local x1 = trackX + trackW - pad - (circleSize * 0.5) - 0.5
local x0_align = textMargin - (circleSize * 0.5)
local x0 = math.max(x0_base, x0_align)
local circleXPrec = x0 + (x1 - x0) * self._circleEased
local circleCenterX = circleXPrec + circleSize * 0.5
local circleCenterY = trackY + trackH * 0.5
local baseCircle = self.value and Mantle.color.theme or Mantle.color.gray
local circleCol = table.Copy(baseCircle)
circleCol.a = 255
self._circleColor = Mantle.func.LerpColor(12, self._circleColor, circleCol)
RNDX().Circle(circleCenterX, circleCenterY, circleSize)
:Color(self._circleColor)
:Draw()
RNDX().Circle(circleCenterX, circleCenterY + 2, circleSize * 1.05)
:Color(Color(0, 0, 0, 30))
:Draw()
end
function PANEL:PerformLayout(w, h)
self.toggle:SetWide(48)
self.toggle:DockMargin(0, 0, 14, 0)
end
vgui.Register('MantleCheckBox', PANEL, 'Panel')

View File

@@ -0,0 +1,222 @@
local color_close = Color(210, 65, 65)
local color_accept = Color(44, 124, 62)
local color_outline = Color(30, 30, 30)
local color_target = Color(255, 255, 255, 200)
function Mantle.ui.color_picker(func, color_standart)
if IsValid(Mantle.ui.menu_color_picker) then
Mantle.ui.menu_color_picker:Remove()
end
local selected_color = color_standart or Color(255, 255, 255)
local hue = 0
local saturation = 1
local value = 1
if color_standart then
local r, g, b = color_standart.r / 255, color_standart.g / 255, color_standart.b / 255
local h, s, v = ColorToHSV(Color(r * 255, g * 255, b * 255))
hue = h
saturation = s
value = v
end
Mantle.ui.menu_color_picker = vgui.Create('MantleFrame')
Mantle.ui.menu_color_picker:SetSize(300, 378)
Mantle.ui.menu_color_picker:Center()
Mantle.ui.menu_color_picker:MakePopup()
Mantle.ui.menu_color_picker:SetTitle('')
Mantle.ui.menu_color_picker:SetCenterTitle(Mantle.lang.get('mantle', 'color_title'))
local container = vgui.Create('Panel', Mantle.ui.menu_color_picker)
container:Dock(FILL)
container:DockMargin(10, 10, 10, 10)
container.Paint = nil
local preview = vgui.Create('Panel', container)
preview:Dock(TOP)
preview:SetTall(40)
preview:DockMargin(0, 0, 0, 10)
preview.Paint = function(self, w, h)
if Mantle.ui.convar.depth_ui then
RNDX().Rect(2, 2, w - 4, h - 4)
:Rad(16)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(5, 20)
:Draw()
end
RNDX.Draw(16, 2, 2, w - 4, h - 4, selected_color, RNDX.SHAPE_IOS)
end
local colorField = vgui.Create('Panel', container)
colorField:Dock(TOP)
colorField:SetTall(200)
colorField:DockMargin(0, 0, 0, 10)
local colorCursor = { x = 0, y = 0 }
local isDraggingColor = false
colorField.OnMousePressed = function(self, keyCode)
if keyCode == MOUSE_LEFT then
isDraggingColor = true
self:OnCursorMoved(self:CursorPos())
Mantle.func.sound()
end
end
colorField.OnMouseReleased = function(self, keyCode)
if keyCode == MOUSE_LEFT then
isDraggingColor = false
end
end
colorField.OnCursorMoved = function(self, x, y)
if isDraggingColor then
local w, h = self:GetSize()
x = math.Clamp(x, 0, w)
y = math.Clamp(y, 0, h)
colorCursor.x = x
colorCursor.y = y
saturation = x / w
value = 1 - (y / h)
selected_color = HSVToColor(hue, saturation, value)
end
end
colorField.Paint = function(self, w, h)
local segments = 80
local segmentSize = w / segments
if Mantle.ui.convar.depth_ui then
RNDX().Rect(0, 0, w, h)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(5, 20)
:Draw()
end
for x = 0, segments do
for y = 0, segments do
local s = x / segments
local v = 1 - (y / segments)
local segX = x * segmentSize
local segY = y * segmentSize
surface.SetDrawColor(HSVToColor(hue, s, v))
surface.DrawRect(segX, segY, segmentSize + 1, segmentSize + 1)
end
end
RNDX().Circle(colorCursor.x, colorCursor.y, 12)
:Outline(2)
:Color(color_target)
:Draw()
end
local hueSlider = vgui.Create('Panel', container)
hueSlider:Dock(TOP)
hueSlider:SetTall(20)
hueSlider:DockMargin(0, 0, 0, 10)
local huePos = 0
local isDraggingHue = false
hueSlider.OnMousePressed = function(self, keyCode)
if keyCode == MOUSE_LEFT then
isDraggingHue = true
self:OnCursorMoved(self:CursorPos())
Mantle.func.sound()
end
end
hueSlider.OnMouseReleased = function(self, keyCode)
if keyCode == MOUSE_LEFT then
isDraggingHue = false
end
end
hueSlider.OnCursorMoved = function(self, x, y)
if isDraggingHue then
local w = self:GetWide()
x = math.Clamp(x, 0, w)
huePos = x
hue = (x / w) * 360
selected_color = HSVToColor(hue, saturation, value)
end
end
hueSlider.Paint = function(self, w, h)
local segments = 100
local segmentWidth = w / segments
if Mantle.ui.convar.depth_ui then
RNDX().Rect(0, 0, w, h)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(5, 20)
:Draw()
end
for i = 0, segments - 1 do
local hueVal = (i / segments) * 360
local x = i * segmentWidth
surface.SetDrawColor(HSVToColor(hueVal, 1, 1))
surface.DrawRect(x, 1, segmentWidth + 1, h - 2)
end
RNDX().Rect(huePos - 2, 0, 4, h)
:Color(color_target)
:Draw()
end
local rgbContainer = vgui.Create('Panel', container)
rgbContainer:Dock(TOP)
rgbContainer:SetTall(60)
rgbContainer:DockMargin(0, 0, 0, 10)
rgbContainer.Paint = nil
local btnContainer = vgui.Create('Panel', container)
btnContainer:Dock(BOTTOM)
btnContainer:SetTall(30)
btnContainer.Paint = nil
local btnClose = vgui.Create('MantleBtn', btnContainer)
btnClose:Dock(LEFT)
btnClose:SetWide(90)
btnClose:SetTxt(Mantle.lang.get('mantle', 'color_cancel'))
btnClose:SetColorHover(color_close)
btnClose.DoClick = function()
Mantle.ui.menu_color_picker:Remove()
Mantle.func.sound()
end
local btnSelect = vgui.Create('MantleBtn', btnContainer)
btnSelect:Dock(RIGHT)
btnSelect:SetWide(90)
btnSelect:SetTxt(Mantle.lang.get('mantle', 'color_select'))
btnSelect:SetColorHover(color_accept)
btnSelect.DoClick = function()
Mantle.func.sound()
func(selected_color)
Mantle.ui.menu_color_picker:Remove()
end
timer.Simple(0, function()
if IsValid(colorField) and IsValid(hueSlider) then
colorCursor.x = saturation * colorField:GetWide()
colorCursor.y = (1 - value) * colorField:GetTall()
huePos = (hue / 360) * hueSlider:GetWide()
end
end)
timer.Simple(0.1, function()
Mantle.ui.menu_color_picker:SetAlpha(255)
end)
end

View File

@@ -0,0 +1,267 @@
local PANEL = {}
function PANEL:Init()
self.choices = {}
self.selected = nil
self.opened = false
self:SetTall(26)
self:SetText('')
self.font = 'Fated.18'
self.hoverAnim = 0
self.OnSelect = function(_, _, _) end
self.btn = vgui.Create('DButton', self)
self.btn:Dock(FILL)
self.btn:SetText('')
self.btn:SetCursor('hand')
self.btn.Paint = function(_, w, h)
if self.btn:IsHovered() then
self.hoverAnim = math.Clamp(self.hoverAnim + FrameTime() * 4, 0, 1)
else
self.hoverAnim = math.Clamp(self.hoverAnim - FrameTime() * 8, 0, 1)
end
if Mantle.ui.convar.depth_ui then
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(5, 20)
:Draw()
end
RNDX.Draw(16, 0, 0, w, h, Mantle.color.focus_panel, RNDX.SHAPE_IOS)
if self.hoverAnim > 0 then
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Color(Mantle.color.button_hovered.r, Mantle.color.button_hovered.g, Mantle.color.button_hovered.b, self.hoverAnim * 255))
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
draw.SimpleText(
self.selected or self.placeholder or 'Выберите...',
self.font,
12,
h * 0.5,
Mantle.color.text,
TEXT_ALIGN_LEFT,
TEXT_ALIGN_CENTER
)
local arrowSize = 6
local arrowX = w - 16
local arrowY = h / 2
local arrowColor = ColorAlpha(Mantle.color.text, 180 + self.hoverAnim * 75)
surface.SetDrawColor(arrowColor)
draw.NoTexture()
if !self.opened then
surface.DrawPoly({
{x = arrowX - arrowSize, y = arrowY - arrowSize/2},
{x = arrowX + arrowSize, y = arrowY - arrowSize/2},
{x = arrowX, y = arrowY + arrowSize/2}
})
end
end
self.btn.DoClick = function()
if self.opened then
self:CloseMenu()
else
self:OpenMenu()
Mantle.func.sound()
end
end
end
function PANEL:AddChoice(text, data)
table.insert(self.choices, {text = text, data = data})
end
function PANEL:SetValue(val)
self.selected = val
end
function PANEL:GetValue()
return self.selected
end
function PANEL:SetPlaceholder(text)
self.placeholder = text
end
function PANEL:OpenMenu()
if IsValid(self.menu) then
self.menu:Remove()
end
local menuPadding = 6
local itemHeight = 26
local menuHeight = (#self.choices * (itemHeight + 2)) + (menuPadding * 2) + 2
local x, y = self:LocalToScreen(0, self:GetTall())
self.menu = vgui.Create('DPanel')
self.menu:SetSize(self:GetWide(), menuHeight)
if y + menuHeight > ScrH() - 10 then
y = y - menuHeight - self:GetTall()
end
self.menu:SetPos(x, y)
self.menu:SetDrawOnTop(true)
self.menu:MakePopup()
self.menu:SetKeyboardInputEnabled(false)
self.menu:DockPadding(menuPadding, menuPadding, menuPadding, menuPadding)
self.menu._anim = 0
self.menu._animTarget = 1
self.menu._animSpeed = 18
self.menu._animEased = 0
self.menu._closing = false
self.menu._disableBlur = false
self.menu:SetAlpha(0)
self.menu.Paint = function(s, w, h)
local aMul = s._animEased or ((s:GetAlpha() or 255) / 255)
local blurMul
if s._closing or s._disableBlur or s._animTarget == 0 then
blurMul = 0
else
local fadeStart = 0.3
blurMul = math.Clamp((aMul - fadeStart) / (1 - fadeStart), 0, 1)
end
local shadowSpread = math.max(0, math.floor(10 * blurMul))
local shadowIntensity = math.max(0, math.floor(16 * blurMul))
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(shadowSpread, shadowIntensity)
:Draw()
if not s._disableBlur then
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Shape(RNDX.SHAPE_IOS)
:Blur(blurMul)
:Draw()
end
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.background_panelpopup)
:Shape(RNDX.SHAPE_IOS)
:Draw()
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.background_panelpopup)
:Shape(RNDX.SHAPE_IOS)
:Outline(1)
:Draw()
end
surface.SetFont(self.font)
for i, choice in ipairs(self.choices) do
local option = vgui.Create('DButton', self.menu)
option:SetText('')
option:Dock(TOP)
option:DockMargin(2, 2, 2, 0)
option:SetTall(itemHeight)
option:SetCursor('hand')
option.Paint = function(s, w, h)
if s:IsHovered() then
RNDX.Draw(16, 0, 0, w, h, Mantle.color.hover, RNDX.SHAPE_IOS)
end
draw.SimpleText(
choice.text,
'Fated.18',
14,
h * 0.5,
Mantle.color.text,
TEXT_ALIGN_LEFT,
TEXT_ALIGN_CENTER
)
if self.selected == choice.text then
RNDX.Draw(0, 4, h * 0.5 - 1, 4, 2, Mantle.color.theme)
end
end
option.DoClick = function()
self.selected = choice.text
if self.menu and IsValid(self.menu) then
self.menu._closing = true
self.menu._animTarget = 0
self.menu._disableBlur = true
end
if self.OnSelect then
self.OnSelect(i, choice.text, choice.data)
end
Mantle.func.sound()
end
end
self.opened = true
local oldMouseDown = false
self.menu.Think = function(s)
local ft = FrameTime()
s._anim = Mantle.func.approachExp(s._anim or 0, s._animTarget or 1, s._animSpeed or 18, ft)
s._animEased = s._anim
s:SetAlpha(math.floor(255 * s._animEased + 0.5))
if s._targetX and s._targetY then
local offsetY = 6 * (1 - s._animEased)
s:SetPos(s._targetX, s._targetY + offsetY)
end
if s._closing and s._animEased <= 0.005 then
s:Remove()
return
end
if not s:IsVisible() then return end
local mouseDown = input.IsMouseDown(MOUSE_LEFT) or input.IsMouseDown(MOUSE_RIGHT)
if mouseDown and not oldMouseDown then
local mx, my = gui.MousePos()
local x, y = s:LocalToScreen(0, 0)
if not (mx >= x and mx <= x + s:GetWide() and my >= y and my <= y + s:GetTall()) then
s._closing = true
s._animTarget = 0
s._disableBlur = true
end
end
oldMouseDown = mouseDown
end
self.menu.OnRemove = function()
if IsValid(self) then
self.opened = false
end
end
Mantle.func.ClampMenuPosition(self.menu)
self.menu._targetX, self.menu._targetY = self.menu:GetPos()
if not self.menu._initPosSet then
self.menu:SetPos(self.menu._targetX, self.menu._targetY + 6)
self.menu._initPosSet = true
end
end
function PANEL:CloseMenu()
if IsValid(self.menu) then
if not self.menu._closing then
self.menu._closing = true
self.menu._animTarget = 0
self.menu._disableBlur = true
end
end
self.opened = false
end
function PANEL:OnRemove()
self:CloseMenu()
end
vgui.Register('MantleComboBox', PANEL, 'Panel')

View File

@@ -0,0 +1,335 @@
local PANEL = {}
function PANEL:Init()
self.Items = {}
self:SetSize(160, 0)
self:DockPadding(4, 5, 4, 5)
self:MakePopup()
self:SetKeyboardInputEnabled(false)
self:SetDrawOnTop(true)
self.MaxTextWidth = 0
self._anim = 0
self._animTarget = 1
self._animSpeed = 18
self._animEased = 0
self._initPosSet = false
self._closing = false
self._disableBlur = false
self._openTime = CurTime()
self:SetAlpha(0)
self.Think = function()
local ft = FrameTime()
if not self._initPosSet then
local tx, ty = self:GetPos()
Mantle.func.ClampMenuPosition(self)
self._targetX, self._targetY = self:GetPos()
self:SetPos(self._targetX, self._targetY + 6)
self._initPosSet = true
end
if CurTime() - self._openTime >= 0.08 then
if input.IsMouseDown(MOUSE_LEFT) or input.IsMouseDown(MOUSE_RIGHT) then
if not self:IsChildHovered() then
self:CloseMenu()
end
end
end
self._anim = Mantle.func.approachExp(self._anim, self._animTarget, self._animSpeed, ft)
self._animEased = self._anim
local a = math.floor(255 * self._animEased + 0.5)
self:SetAlpha(a)
if self._targetX and self._targetY then
local offsetY = 6 * (1 - self._animEased)
self:SetPos(self._targetX, self._targetY + offsetY)
end
if self._closing and self._animEased <= 0.005 then
return self:Remove()
end
end
end
function PANEL:Paint(w, h)
local aMul = (self._animEased ~= nil) and self._animEased or ((self:GetAlpha() or 255) / 255)
local blurMul
if self._closing or self._disableBlur or self._animTarget == 0 then
blurMul = 0
else
local fadeStart = 0.3
blurMul = math.Clamp((aMul - fadeStart) / (1 - fadeStart), 0, 1)
end
local shadowSpread = math.max(0, math.floor(10 * blurMul))
local shadowIntensity = math.max(0, math.floor(16 * blurMul))
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Color(Mantle.color.window_shadow.r, Mantle.color.window_shadow.g, Mantle.color.window_shadow.b, math.floor(100 * aMul)))
:Shape(RNDX.SHAPE_IOS)
:Shadow(shadowSpread, shadowIntensity)
:Draw()
if !self._disableBlur then
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Shape(RNDX.SHAPE_IOS)
:Blur(blurMul)
:Draw()
end
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math.floor(150 * aMul)))
:Shape(RNDX.SHAPE_IOS)
:Draw()
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math.floor(150 * aMul)))
:Shape(RNDX.SHAPE_IOS)
:Outline(1)
:Draw()
end
function PANEL:AddOption(text, func, icon, optData)
surface.SetFont('Fated.18')
local textW = select(1, surface.GetTextSize(text))
self.MaxTextWidth = math.max(self.MaxTextWidth or 0, textW)
local option = vgui.Create('DButton', self)
option:SetText('')
option:Dock(TOP)
option:DockMargin(2, 2, 2, 0)
option:SetTall(26)
option.sumTall = 28
option.Icon = icon
option.Text = text
option._submenu = nil
option._submenu_open = false
option.DoClick = function()
if option._submenu then
if option._submenu_open then
option:CloseSubMenu()
else
option:OpenSubMenu()
end
return
end
if func then func() end
Mantle.func.sound()
local function closeAllMenus(panel)
while IsValid(panel) do
if panel.GetName and panel:GetName() == 'MantleDermaMenu' then
local parent = panel:GetParent()
panel:CloseMenu()
panel = parent
else
panel = panel:GetParent()
end
end
end
closeAllMenus(option)
end
function option:AddSubMenu()
if IsValid(option._submenu) then option._submenu:Remove() end
local submenu = vgui.Create('MantleDermaMenu')
submenu:SetDrawOnTop(true)
submenu:SetParent(self:GetParent())
submenu:SetVisible(false)
option._submenu = submenu
option._submenu_open = false
option.OnRemove = function()
if IsValid(submenu) then submenu:Remove() end
end
function option:OpenSubMenu()
if not IsValid(submenu) then return end
for _, sibling in ipairs(self:GetParent().Items or {}) do
if sibling ~= self and sibling.CloseSubMenu then sibling:CloseSubMenu() end
end
local x, y = self:LocalToScreen(self:GetWide(), 0)
submenu:SetPos(x, y)
Mantle.func.ClampMenuPosition(submenu)
submenu._targetX, submenu._targetY = submenu:GetPos()
submenu:SetVisible(true)
submenu:MakePopup()
submenu:SetKeyboardInputEnabled(false)
option._submenu_open = true
end
function option:CloseSubMenu()
if IsValid(submenu) then submenu:SetVisible(false) end
option._submenu_open = false
if submenu.Items then
for _, item in ipairs(submenu.Items) do
if item.CloseSubMenu then item:CloseSubMenu() end
end
end
end
local function isAnySubmenuHovered(opt)
if not IsValid(opt) then return false end
if opt:IsHovered() then return true end
if opt._submenu and IsValid(opt._submenu) and opt._submenu:IsVisible() then
if isAnySubmenuHovered(opt._submenu) then return true end
for _, item in ipairs(opt._submenu.Items or {}) do
if isAnySubmenuHovered(item) then return true end
end
end
return false
end
option.OnCursorExited = function(pnl)
timer.Simple(0.15, function()
if not isAnySubmenuHovered(pnl) then
if IsValid(pnl) then
pnl:CloseSubMenu()
end
end
end)
end
submenu.OnCursorExited = function(pnl)
timer.Simple(0.15, function()
if not isAnySubmenuHovered(option) then
if IsValid(pnl) then
option:CloseSubMenu()
end
end
end)
end
return submenu
end
option.AddSubMenu = option.AddSubMenu
if optData then
for k, v in pairs(optData) do
option[k] = v
end
end
local iconMat
if option.Icon then
iconMat = type(option.Icon) == 'IMaterial' and option.Icon or Material(option.Icon)
end
option.Paint = function(pnl, w, h)
w = w or pnl:GetWide()
h = h or pnl:GetTall()
if pnl:IsHovered() then
if Mantle.ui.convar.depth_ui then
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(5, 20)
:Draw()
end
RNDX.Draw(16, 0, 0, w, h, Mantle.color.hover, RNDX.SHAPE_IOS)
if pnl._submenu and not pnl._submenu_open then
pnl:OpenSubMenu()
end
end
if iconMat then
local iconSize = 16
RNDX.DrawMaterial(0, 10, (h - iconSize) / 2, iconSize, iconSize, color_white, iconMat)
end
draw.SimpleText(pnl.Text, 'Fated.18', pnl.Icon and 32 or 14, h * 0.5, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
table.insert(self.Items, option)
self:UpdateSize()
return option
end
function PANEL:AddSpacer()
local spacer = vgui.Create('DPanel', self)
spacer:Dock(TOP)
spacer:DockMargin(8, 6, 8, 6)
spacer:SetTall(1)
spacer.sumTall = 13
spacer.Paint = function(_, w, h)
RNDX.Draw(0, 0, 0, w, h, Mantle.color.focus_panel)
end
table.insert(self.Items, spacer)
self:UpdateSize()
return spacer
end
function PANEL:UpdateSize()
local height = 12
for _, item in ipairs(self.Items) do
if IsValid(item) then
height = height + (item.sumTall or item:GetTall())
end
end
local maxWidth = math.max(160, self.MaxTextWidth + 56)
self:SetSize(maxWidth, math.min(height, ScrH() * 0.8))
if not self._targetX or not self._targetY then
Mantle.func.ClampMenuPosition(self)
self._targetX, self._targetY = self:GetPos()
if not self._initPosSet then
self:SetPos(self._targetX, self._targetY + 6)
end
else
Mantle.func.ClampMenuPosition(self)
self._targetX, self._targetY = self:GetPos()
end
end
function PANEL:Open()
-- Clear
end
function PANEL:CloseMenu()
if self._closing then return end
self._closing = true
self._disableBlur = true
self._animTarget = 0
end
function PANEL:GetDeleteSelf()
return true
end
vgui.Register('MantleDermaMenu', PANEL, 'DPanel')
function Mantle.ui.derma_menu()
if IsValid(Mantle.ui.menu_derma_menu) then
Mantle.ui.menu_derma_menu:CloseMenu()
end
local mouseX, mouseY = input.GetCursorPos()
local m = vgui.Create('MantleDermaMenu')
m:SetPos(mouseX, mouseY)
Mantle.func.ClampMenuPosition(m)
m._targetX, m._targetY = m:GetPos()
Mantle.ui.menu_derma_menu = m
return m
end

View File

@@ -0,0 +1,98 @@
local PANEL = {}
function PANEL:Init()
self.title = nil
self.placeholder = Mantle.lang.get('mantle', 'entry_default_placeholder')
self:SetTall(26)
self.action = function() end
local font = 'Fated.18'
self.textEntry = vgui.Create('DTextEntry', self)
self.textEntry:Dock(FILL)
self.textEntry:SetText('')
self.textEntry.OnCloseFocus = function()
self.action(self:GetValue())
end
self._text_offset = 0
self._shadowLerp = 5
self.textEntry.Paint = nil
self.textEntry.PaintOver = function(s, w, h)
local ft = FrameTime()
if Mantle.ui.convar.depth_ui then
local target = s:IsEditing() and 10 or 5
self._shadowLerp = Mantle.func.approachExp(self._shadowLerp, target, 12, ft)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.window_shadow)
:Shape(RNDX.SHAPE_IOS)
:Shadow(self._shadowLerp, 20)
:Draw()
end
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.focus_panel)
:Shape(RNDX.SHAPE_IOS)
:Draw()
local value = self:GetValue() or ''
surface.SetFont(font)
local padding = 6
local available_w = w - padding * 2
local caret = #value
local before_caret = string.sub(value, 1, caret)
local caret_x = surface.GetTextSize(before_caret)
local text_w = surface.GetTextSize(value)
local desired_offset = 0
if caret_x > available_w then
desired_offset = caret_x - available_w
end
if text_w - desired_offset < available_w then
desired_offset = math.max(0, text_w - available_w)
end
self._text_offset = Mantle.func.approachExp(self._text_offset or 0, desired_offset, 24, ft)
local text = self.placeholder
local col = Mantle.color.gray
if value != '' then
text = value
col = Mantle.color.text
end
draw.SimpleText(text, font, padding - self._text_offset, h * 0.5, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
end
function PANEL:SetTitle(title)
self.title = title
self:SetTall(52)
if IsValid(self.titlePanel) then
self.titlePanel:Remove()
end
self.titlePanel = vgui.Create('DPanel', self)
self.titlePanel:Dock(TOP)
self.titlePanel:DockMargin(0, 0, 0, 6)
self.titlePanel:SetTall(18)
self.titlePanel.Paint = function(_, w, h)
draw.SimpleText(self.title, 'Fated.18', 0, 0, Mantle.color.text)
end
end
function PANEL:SetPlaceholder(placeholder)
self.placeholder = placeholder
end
function PANEL:GetValue()
return self.textEntry:GetText()
end
vgui.Register('MantleEntry', PANEL, 'EditablePanel')

View File

@@ -0,0 +1,194 @@
local PANEL = {}
local mat_close = Material('mantle/close_btn_new.png')
function PANEL:Init()
self.bool_alpha = true
self.bool_lite = false
self.title = Mantle.lang.get('mantle', 'frame_title')
self.center_title = ''
self:DockPadding(6, 30, 6, 6)
self.top_panel = vgui.Create('DButton', self)
self.top_panel:SetText('')
self.top_panel:SetCursor('sizeall')
self.top_panel.Paint = nil
self.top_panel.OnMousePressed = function(s, key)
if key == MOUSE_LEFT then
self.Dragging = {gui.MouseX() - self.x, gui.MouseY() - self.y}
s:MouseCapture(true)
self:SetAlpha(200)
end
end
self.top_panel.OnMouseReleased = function(s, key)
if key == MOUSE_LEFT then
self.Dragging = nil
s:MouseCapture(false)
self:SetAlpha(255)
end
end
self.top_panel.Think = function(s)
if self.Dragging then
local mouseX, mouseY = gui.MousePos()
local newPosX, newPosY = mouseX - self.Dragging[1], mouseY - self.Dragging[2]
self:SetPos(newPosX, newPosY)
end
end
self.cls = vgui.Create('Button', self)
self.cls:SetText('')
self.cls.Paint = function(_, w, h)
RNDX().Rect(2, 2, w - 4, h - 4)
:Color(Mantle.color.header_text)
:Material(mat_close)
:Draw()
end
self.cls.DoClick = function()
self:AlphaTo(0, 0.1, 0, function()
self:Remove()
end)
Mantle.func.sound()
end
self.cls.DoRightClick = function()
local DM = Mantle.ui.derma_menu()
DM:AddOption(Mantle.lang.get('mantle', 'frame_alpha'), function()
self.bool_alpha = !self.bool_alpha
end, self.bool_alpha and 'icon16/bullet_green.png' or 'icon16/bullet_red.png')
local boolInput = self:IsKeyboardInputEnabled()
DM:AddOption(Mantle.lang.get('mantle', 'frame_move_from_menu'), function()
self:SetKeyBoardInputEnabled(!boolInput)
end, !boolInput and 'icon16/bullet_green.png' or 'icon16/bullet_red.png')
DM:AddOption(Mantle.lang.get('mantle', 'frame_close_window'), function()
self:Remove()
end, 'icon16/cross.png')
end
end
function PANEL:SetAlphaBackground(is_alpha)
self.bool_alpha = is_alpha
end
function PANEL:SetTitle(title)
self.title = title
end
function PANEL:SetCenterTitle(center_title)
self.center_title = center_title
end
function PANEL:ShowAnimation()
Mantle.func.animate_appearance(self, self:GetWide(), self:GetTall(), 0.3, 0.2)
end
function PANEL:DisableCloseBtn()
self.cls:SetVisible(false)
end
function PANEL:SetDraggable(is_draggable)
self.top_panel:SetVisible(is_draggable)
end
function PANEL:LiteMode()
self.bool_lite = true
self:DockPadding(6, 6, 6, 6)
self.cls:SetZPos(2)
end
function PANEL:Notify(text, duration, col)
if IsValid(self.messagePanel) then self.messagePanel:Remove() end
duration = duration or 2
col = col or Mantle.color.theme
surface.SetFont('Fated.20')
local tw, th = surface.GetTextSize(text)
local mp = vgui.Create('DPanel', self)
mp:SetSize(tw + 16, th + 8)
mp:SetMouseInputEnabled(false)
local startY = self:GetTall() + mp:GetTall()
local endY = self:GetTall() - mp:GetTall() - 16
mp:SetPos((self:GetWide() - mp:GetWide()) * 0.5, startY)
mp:SetAlpha(0)
mp.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(col)
:Shadow(7, 20)
:Outline(3)
:Clip(self)
:Draw()
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(col)
:Draw()
draw.SimpleText(text, 'Fated.20', w * 0.5, h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
mp:MoveTo(mp.x, endY, 0.3, 0, 0.7)
mp:AlphaTo(255, 0.3, 0, function()
timer.Simple(duration, function()
if !IsValid(mp) then return end
mp:AlphaTo(0, 0.25, 0, function()
if IsValid(mp) then
mp:Remove()
end
end)
end)
end)
self.messagePanel = mp
end
local flagsHeader = RNDX.NO_BL + RNDX.NO_BR
local flagsBackground = RNDX.NO_TL + RNDX.NO_TR
function PANEL:Paint(w, h)
RNDX().Rect(0, 0, w, h)
:Rad(6)
:Color(Mantle.color.window_shadow)
:Shadow(10, 16)
:Shape(RNDX.SHAPE_IOS)
:Draw()
if !self.bool_lite then
RNDX().Rect(0, 0, w, 24)
:Radii(6, 6, 0, 0)
:Color(Mantle.color.header)
:Draw()
end
local headerTall = self.bool_lite and 0 or 24
if self.bool_alpha and Mantle.ui.convar.blur then
RNDX().Rect(0, headerTall, w, h - headerTall)
:Radii(self.bool_lite and 6 or 0, self.bool_lite and 6 or 0, 6, 6)
:Blur()
:Draw()
end
RNDX().Rect(0, headerTall, w, h - headerTall)
:Radii(self.bool_lite and 6 or 0, self.bool_lite and 6 or 0, 6, 6)
:Color((self.bool_alpha and Mantle.ui.convar.blur) and Mantle.color.background_alpha or Mantle.color.background)
:Draw()
if !self.bool_lite then
if self.center_title != '' then
draw.SimpleText(self.center_title, 'Fated.20b', w * 0.5, 12, Mantle.color.header_text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
draw.SimpleText(self.title, 'Fated.16', 6, 4, Mantle.color.header_text)
end
end
function PANEL:PerformLayout(w, h)
self.top_panel:SetSize(w, 24)
self.cls:SetSize(20, 20)
self.cls:SetPos(w - 22, 2)
end
vgui.Register('MantleFrame', PANEL, 'EditablePanel')

View File

@@ -0,0 +1,115 @@
local color_disconnect = Color(210, 65, 65)
local color_bot = Color(70, 150, 220)
local color_online = Color(120, 180, 70)
function Mantle.ui.player_selector(do_click, func_check)
if IsValid(Mantle.ui.menu_player_selector) then
Mantle.ui.menu_player_selector:Remove()
end
Mantle.ui.menu_player_selector = vgui.Create('MantleFrame')
Mantle.ui.menu_player_selector:SetSize(340, 398)
Mantle.ui.menu_player_selector:Center()
Mantle.ui.menu_player_selector:MakePopup()
Mantle.ui.menu_player_selector:SetTitle('')
Mantle.ui.menu_player_selector:SetCenterTitle(Mantle.lang.get('mantle', 'player_title'))
Mantle.ui.menu_player_selector:ShowAnimation()
local contentPanel = vgui.Create('Panel', Mantle.ui.menu_player_selector)
contentPanel:Dock(FILL)
contentPanel:DockMargin(8, 0, 8, 8)
Mantle.ui.menu_player_selector.sp = vgui.Create('MantleScrollPanel', contentPanel)
Mantle.ui.menu_player_selector.sp:Dock(FILL)
local CARD_HEIGHT = 44
local AVATAR_SIZE = 32
local AVATAR_X = 14
local function CreatePlayerCard(pl)
local card = vgui.Create('DButton', Mantle.ui.menu_player_selector.sp)
card:Dock(TOP)
card:DockMargin(0, 5, 0, 0)
card:SetTall(CARD_HEIGHT)
card:SetText('')
card.hover_status = 0
card.OnCursorEntered = function(self)
self:SetCursor('hand')
end
card.OnCursorExited = function(self)
self:SetCursor('arrow')
end
card.Think = function(self)
local target = self:IsHovered() and 1 or 0
self.hover_status = Mantle.func.approachExp(self.hover_status, target, 8, FrameTime())
end
card.DoClick = function()
if IsValid(pl) then
Mantle.func.sound()
do_click(pl)
end
Mantle.ui.menu_player_selector:Remove()
end
card.pl_color = team.GetColor(pl:Team()) or color_online
card.Paint = function(self, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(10)
:Color(Mantle.color.panel[1])
:Shape(RNDX.SHAPE_IOS)
:Draw()
if self.hover_status > 0 then
RNDX().Rect(0, 0, w, h)
:Rad(10)
:Color(Color(0, 0, 0, 40 * self.hover_status))
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
local infoX = AVATAR_X + AVATAR_SIZE + 10
if !IsValid(pl) then
draw.SimpleText(Mantle.lang.get('mantle', 'player_offline'), 'Fated.18', infoX, h * 0.5, color_disconnect, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
return
end
draw.SimpleText(pl:Name(), 'Fated.18', infoX, 6, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
local group = pl:GetUserGroup() or 'user'
group = string.upper(string.sub(group, 1, 1)) .. string.sub(group, 2)
draw.SimpleText(group, 'Fated.14', infoX, h - 6, Mantle.color.gray, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM)
draw.SimpleText(pl:Ping() .. ' ' .. Mantle.lang.get('mantle', 'player_ping'), 'Fated.16', w - 20, h - 6, Mantle.color.gray, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM)
local statusColor = color_disconnect
if pl:IsBot() then
statusColor = color_bot
else
statusColor = self.pl_color
end
RNDX.DrawCircle(w - 24, 14, 12, statusColor)
end
local avatarImg = vgui.Create('AvatarImage', card)
avatarImg:SetSize(AVATAR_SIZE, AVATAR_SIZE)
avatarImg:SetPos(AVATAR_X, (CARD_HEIGHT - AVATAR_SIZE) * 0.5)
avatarImg:SetSteamID(pl:SteamID64(), 64)
avatarImg:SetMouseInputEnabled(false)
avatarImg:SetKeyboardInputEnabled(false)
avatarImg.PaintOver = function() end
avatarImg:SetPos(AVATAR_X, (card:GetTall() - AVATAR_SIZE) * 0.5)
return card
end
for _, pl in player.Iterator() do
CreatePlayerCard(pl)
end
Mantle.ui.menu_player_selector.btn_close = vgui.Create('MantleBtn', Mantle.ui.menu_player_selector)
Mantle.ui.menu_player_selector.btn_close:Dock(BOTTOM)
Mantle.ui.menu_player_selector.btn_close:DockMargin(16, 8, 16, 12)
Mantle.ui.menu_player_selector.btn_close:SetTall(36)
Mantle.ui.menu_player_selector.btn_close:SetTxt(Mantle.lang.get('mantle', 'player_close'))
Mantle.ui.menu_player_selector.btn_close:SetColorHover(color_disconnect)
Mantle.ui.menu_player_selector.btn_close.DoClick = function()
Mantle.ui.menu_player_selector:Remove()
end
end

View File

@@ -0,0 +1,458 @@
local PANEL = {}
local pi = math.pi
local math_cos = math.cos
local math_sin = math.sin
local math_atan2 = math.atan2
local math_sqrt = math.sqrt
local math_floor = math.floor
local math_min = math.min
local math_max = math.max
local FrameTime = FrameTime
local SysTime = SysTime
local CurTime = CurTime
local Lerp = Lerp
local EPS = 1e-6
local EPS_ANGLE = 1e-4
local function GetSectorIndexFromAngle(angle, cnt)
if !angle or cnt <= 0 then return nil end
local sector = (2 * pi) / cnt
local raw = angle / sector
local idx = (math_floor(raw + EPS) % cnt) + 1
return idx
end
local function ClampEndAngle(a)
if a >= 360 then
return 360 - EPS_ANGLE
end
return a
end
function PANEL:Init(options)
options = options or {}
self.options = {}
self.rootMenu = { title = 'Меню', desc = 'Выберите опцию', options = self.options }
self.menuStack = {}
self.currentMenu = self.rootMenu
local baseRadius = options.radius or 320
local baseInner = options.inner_radius or 110
local minW, minH = 1366, 768
local scale = 1
if Mantle.func.sw > minW and Mantle.func.sh > minH then
scale = math_min(math_min(Mantle.func.sw / 1920, Mantle.func.sh / 1080), 1.15)
end
self.radius = Mantle.func.w(baseRadius) * scale
self.innerRadius = Mantle.func.w(baseInner) * scale
self.scale = scale
self.titleFont = 'Fated.28'
self.font = 'Fated.20'
self.descFont = 'Fated.14'
self.fadeInTime = 0.18
self.openTime = SysTime()
self.currentAlpha = 0
self.scaleAnim = 0.96
self.scale_animation = options.scale_animation != false
self.disable_background = options.disable_background or false
self.hover_sound = options.hover_sound or 'mantle/ratio_btn.ogg'
self.hoverOption = nil
self.hoverAnim = 0
self.selectedOption = nil
self._hotkeyCooldown = {}
self.optionHover = {}
self:SetSize(Mantle.func.sw, Mantle.func.sh)
self:SetPos(0, 0)
self:MakePopup()
self:SetKeyboardInputEnabled(true)
self:SetDrawOnTop(true)
self:SetMouseInputEnabled(true)
self._mouseWasDown = false
self.Think = function()
if self.currentAlpha < 255 then
self.currentAlpha = math.Clamp(255 * ((SysTime() - self.openTime) / self.fadeInTime), 0, 255)
if self.scale_animation then
local t = math.Clamp((SysTime() - self.openTime) / self.fadeInTime, 0, 1)
self.scaleAnim = 0.96 + (1 - (1 - t)^2) * 0.04
else
self.scaleAnim = 1
end
end
local curOuter = self.radius * self.scaleAnim
local curInner = self.innerRadius * self.scaleAnim
local mouseDown = input.IsMouseDown(MOUSE_LEFT)
if mouseDown and !self._mouseWasDown then
local mx, my = self:CursorPos()
local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2
local dist = math_sqrt((mx - cx)^2 + (my - cy)^2)
if dist > curInner and dist < curOuter then
local ang = math_atan2(my - cy, mx - cx)
if ang < 0 then ang = ang + 2 * pi end
local opts = self:GetCurrentOptions()
local cnt = #opts
if cnt > 0 then
local idx = GetSectorIndexFromAngle(ang, cnt)
if idx and opts[idx] then
self:SelectOption(idx)
if self.hover_sound then surface.PlaySound(self.hover_sound) end
end
end
elseif dist <= curInner then
if #self.menuStack > 0 then
self:GoBack()
if self.hover_sound then surface.PlaySound(self.hover_sound) end
else
self:Remove()
end
else
if dist >= curOuter then
self:Remove()
end
end
end
local mx, my = self:CursorPos()
local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2
local dist = math_sqrt((mx - cx)^2 + (my - cy)^2)
local hovered = nil
if dist > curInner and dist < curOuter then
local ang = math_atan2(my - cy, mx - cx)
if ang < 0 then ang = ang + 2 * pi end
local opts = self:GetCurrentOptions()
local cnt = #opts
if cnt > 0 then
hovered = GetSectorIndexFromAngle(ang, cnt)
end
end
if self.hoverOption != hovered and hovered and self.hover_sound then
surface.PlaySound(self.hover_sound)
end
self.hoverOption = hovered
self.hoverAnim = math.Clamp(self.hoverAnim + (self.hoverOption and 10 or -20) * FrameTime(), 0, 1)
self._mouseWasDown = mouseDown
local dt = FrameTime()
local opts = self:GetCurrentOptions()
for i = 1, #opts do
local target = (self.hoverOption == i) and 1 or 0
self.optionHover[i] = Mantle.func.approachExp(self.optionHover[i] or 0, target, 18, dt)
end
for i = 1, math_min(9, #self:GetCurrentOptions()) do
local k = KEY_1 + (i-1)
if input.IsKeyDown(k) then
local last = self._hotkeyCooldown[k] or 0
if CurTime() - last > 0.18 then
self._hotkeyCooldown[k] = CurTime()
self:SelectOption(i)
if self.hover_sound then surface.PlaySound(self.hover_sound) end
end
end
end
end
end
function PANEL:OnMousePressed(k)
local mx, my = self:CursorPos()
local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2
local curOuter = self.radius * self.scaleAnim
local dist = math_sqrt((mx - cx)^2 + (my - cy)^2)
if dist <= curOuter then return self:MouseCapture(true) end
self:Remove()
return true
end
function PANEL:OnMouseReleased(k) self:MouseCapture(false) end
function PANEL:CreateSubMenu(title, desc)
local submenu = { title = title or 'Подменю', desc = desc or '', options = {} }
function submenu:AddOption(text, func, icon, desc)
table.insert(submenu.options, { text = text, func = func, icon = icon, desc = desc })
return #submenu.options
end
return submenu
end
function PANEL:AddSubMenuOption(text, submenu, icon, desc)
return self:AddOption(text, nil, icon, desc, submenu)
end
function PANEL:AddOption(text, func, icon, desc, submenu)
table.insert(self.options, { text = text, func = func, icon = icon, desc = desc, submenu = submenu })
return #self.options
end
function PANEL:GetCurrentOptions()
if self.currentMenu and self.currentMenu.options then
return self.currentMenu.options
end
return self.options
end
function PANEL:SelectOption(index)
local opts = self:GetCurrentOptions()
if !opts or !opts[index] then return end
local opt = opts[index]
if opt.submenu then
table.insert(self.menuStack, self.currentMenu)
self.currentMenu = opt.submenu
self:UpdateCenterText()
return
end
self.selectedOption = opt
if opt.func then
local ok, err = pcall(opt.func)
if !ok then ErrorNoHalt(tostring(err) .. '\n') end
end
self:Remove()
end
function PANEL:GoBack()
if #self.menuStack > 0 then
self.currentMenu = table.remove(self.menuStack)
self:UpdateCenterText()
end
end
function PANEL:SetCenterText(title, desc)
self.rootMenu.title = title or self.rootMenu.title
self.rootMenu.desc = desc or self.rootMenu.desc
self:UpdateCenterText()
end
function PANEL:UpdateCenterText()
if self.currentMenu then
self.centerText = self.currentMenu.title or self.rootMenu.title
self.centerDesc = self.currentMenu.desc or self.rootMenu.desc
else
self.centerText = self.rootMenu.title
self.centerDesc = self.rootMenu.desc
end
end
function PANEL:IsMouseOver()
local mx, my = self:CursorPos()
local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2
local curOuter = self.radius * self.scaleAnim
return math_sqrt((mx - cx)^2 + (my - cy)^2) <= curOuter
end
function PANEL:OnCursorMoved(x,y)
if !self:IsMouseOver() then self.hoverOption = nil end
end
function PANEL:OnRemove()
if Mantle.ui.menu_radial == self then Mantle.ui.menu_radial = nil end
end
function PANEL:Paint(w,h)
local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2
local alpha = math.Clamp(self.currentAlpha / 255, 0, 1)
local opts = self:GetCurrentOptions()
local cnt = #opts
if !self.disable_background then
RNDX().Rect(0, 0, w, h)
:Radii(0, 0, 0, 0)
:Color(Color(0, 0, 0, 140 * alpha))
:Draw()
end
local outerR = self.radius * self.scaleAnim
local innerR = self.innerRadius * self.scaleAnim
local outerD = outerR * 2
local innerD = innerR * 2
RNDX().Circle(cx, cy, outerD + 12)
:Color(Mantle.color.window_shadow)
:Shadow(8, 24)
:Draw()
RNDX().Circle(cx, cy, outerD)
:Color(Color(Mantle.color.background.r, Mantle.color.background.g, Mantle.color.background.b, math_floor(240 * alpha)))
:Draw()
RNDX().Circle(cx, cy, outerD)
:Outline(2)
:Color(Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(160 * alpha)))
:Draw()
if cnt > 0 then
local sectorDeg = 360 / cnt
local baseCol = Mantle.color.background_panelpopup
local baseSectorCol = Color(baseCol.r, baseCol.g, baseCol.b, math_floor(255 * alpha))
for i = 1, cnt do
local startDeg = (i-1) * sectorDeg
local endDeg = i * sectorDeg
if startDeg < 0 then startDeg = 0 end
endDeg = ClampEndAngle(endDeg)
if endDeg > startDeg then
RNDX().Circle(cx, cy, outerD)
:StartAngle(startDeg)
:EndAngle(endDeg)
:Color(baseSectorCol)
:Draw()
RNDX().Circle(cx, cy, outerD)
:StartAngle(startDeg)
:EndAngle(endDeg)
:Outline(2)
:Color(Color(Mantle.color.panel[1].r, Mantle.color.panel[1].g, Mantle.color.panel[1].b, math_floor(160 * alpha)))
:Draw()
end
end
if self.hoverOption and opts[self.hoverOption] then
local i = self.hoverOption
local startDeg = (i-1) * sectorDeg
local endDeg = i * sectorDeg
if startDeg < 0 then startDeg = 0 end
endDeg = ClampEndAngle(endDeg)
if endDeg > startDeg then
local th = Mantle.color.theme
local hoverAlpha = math_floor(200 * self.hoverAnim * alpha)
RNDX().Circle(cx, cy, outerD)
:StartAngle(startDeg)
:EndAngle(endDeg)
:Color(Color(th.r, th.g, th.b, math_floor(22 * self.hoverAnim * alpha)))
:Draw()
RNDX().Circle(cx, cy, outerD)
:StartAngle(startDeg)
:EndAngle(endDeg)
:Outline(2)
:Color(Color(th.r, th.g, th.b, hoverAlpha))
:Draw()
end
end
RNDX().Circle(cx, cy, innerD)
:Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math_floor(255 * alpha)))
:Draw()
local tintA = math_floor(36 * alpha)
RNDX().Circle(cx, cy, innerD - 8)
:Color(Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, tintA))
:Draw()
RNDX().Circle(cx, cy, innerD)
:Outline(2)
:Color(Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(80 * alpha)))
:Draw()
local sectorRad = (2 * pi) / cnt
for i, option in ipairs(opts) do
local startA = (i - 1) * sectorRad
local midA = startA + sectorRad * 0.5
local hv = self.optionHover[i] or 0
local eased = Mantle.func.easeOutCubic(math.Clamp(hv, 0, 1))
local labelR = innerR + (outerR - innerR) * (0.5 + 0.06 * eased)
local numberR = innerR + (labelR - innerR) * 0.35
local lx = cx + labelR * math_cos(midA)
local ly = cy + labelR * math_sin(midA)
local nx = cx + numberR * math_cos(midA)
local ny = cy + numberR * math_sin(midA)
local isHovered = (self.hoverOption == i)
local txtAlpha = math_floor((isHovered and 255 or 220) * alpha)
local txtCol = Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, txtAlpha)
if option.icon and option.icon != false and option.icon != nil then
local iconSize = Mantle.func.w(28) * self.scale * (1 + 0.06 * eased)
local iconX = lx - iconSize * 0.5
local iconY = ly - iconSize * 0.5 - Mantle.func.h(6) * self.scale
local mat = Material(option.icon)
if mat and !mat:IsError() then
surface.SetDrawColor(255,255,255, math_floor(230 * alpha))
surface.SetMaterial(mat)
surface.DrawTexturedRect(iconX, iconY, iconSize, iconSize)
end
draw.SimpleText(option.text or '', self.font, lx, ly + iconSize*0.5 - Mantle.func.h(4)*self.scale, txtCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
if option.desc and isHovered then
draw.SimpleText(option.desc, self.descFont, lx, ly + iconSize*0.5 + Mantle.func.h(16)*self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
end
if i <= 9 then
draw.SimpleText(tostring(i), 'Fated.14', nx, ny, Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(200 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
else
draw.SimpleText(option.text or '', self.font, lx, ly - Mantle.func.h(4) * self.scale, txtCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if option.desc and isHovered then
draw.SimpleText(option.desc, self.descFont, lx, ly + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
end
if i <= 9 then
draw.SimpleText(tostring(i), 'Fated.14', nx, ny, Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(200 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
end
else
RNDX().Circle(cx, cy, outerD)
:Color(Color(Mantle.color.background.r, Mantle.color.background.g, Mantle.color.background.b, math_floor(240 * alpha)))
:Draw()
RNDX().Circle(cx, cy, innerD)
:Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math_floor(255 * alpha)))
:Draw()
end
if self.selectedOption then
local opt = self.selectedOption
if opt.icon and opt.icon != false and opt.icon != nil then
local isz = Mantle.func.w(48) * self.scale
local mat = Material(opt.icon)
if mat and !mat:IsError() then
surface.SetDrawColor(255, 255, 255, math_floor(255 * alpha))
surface.SetMaterial(mat)
surface.DrawTexturedRect(cx - isz/2, cy - isz/2 - Mantle.func.h(6)*self.scale, isz, isz)
end
draw.SimpleText(opt.text or '', self.titleFont, cx + isz*0.6, cy - Mantle.func.h(6) * self.scale, Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, math_floor(255 * alpha)), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
if opt.desc then
draw.SimpleText(opt.desc, self.descFont, cx + isz*0.6, cy + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
else
draw.SimpleText(opt.text or '', self.titleFont, cx, cy - Mantle.func.h(6) * self.scale, Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, math_floor(255 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if opt.desc then
draw.SimpleText(opt.desc, self.descFont, cx, cy + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
else
draw.SimpleText(self.centerText or self.rootMenu.title, self.titleFont, cx, cy - Mantle.func.h(8) * self.scale, Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, math_floor(255 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText(self.centerDesc or self.rootMenu.desc, self.descFont, cx, cy + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(160 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
vgui.Register('MantleRadialPanel', PANEL, 'DPanel')
function Mantle.ui.radial_menu(options)
if IsValid(Mantle.ui.menu_radial) then
Mantle.ui.menu_radial:Remove()
end
local m = vgui.Create('MantleRadialPanel')
m:Init(options)
Mantle.ui.menu_radial = m
return m
end

View File

@@ -0,0 +1,498 @@
local PANEL = {}
function PANEL:Init()
self._vbarPadRight = 6
self.content = vgui.Create('Panel', self)
self.content:SetMouseInputEnabled(true)
self.vbar = vgui.Create('Panel', self)
self.vbar:SetMouseInputEnabled(true)
self.vbarDefaultWidth = 4
self.vbarExpandedWidth = 6
self.vbarWidthSpeed = 12
self.vbarReserveWidth = self.vbarExpandedWidth
self.vbar:SetWide(self.vbarDefaultWidth)
self.vbar.Dragging = false
self.vbar._press_off = 0
self.vbar:Dock(RIGHT)
self.vbar:DockMargin(6, 0, 0, 0)
self.vbar.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.focus_panel)
:Draw()
end
self.vbarHoverDelay = 1
self.vbarUnhoverDelay = 0.5
self.vbar._hoverEnter = 0
self.vbar._hoverExit = 0
self.vbar._expanded = false
self.vbar.btnGrip = vgui.Create('MantleBtn', self.vbar)
self.vbar.btnGrip:SetText('')
self.vbar.btnGrip._ShadowLerp = 0
self.vbar.btnGrip.Paint = function(s, w, h)
s._ShadowLerp = Lerp(FrameTime() * 10, s._ShadowLerp, self.vbar.Dragging and 7 or 0)
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.theme)
:Shadow(s._ShadowLerp, 20)
:Draw()
RNDX().Rect(0, 0, w, h)
:Rad(32)
:Color(Mantle.color.theme)
:Draw()
end
self.vbar.btnGrip.OnMousePressed = function(s)
local _, my = s:GetParent():CursorPos()
s:GetParent().Dragging = true
s:GetParent()._press_off = my - s.y
s:MouseCapture(true)
s:GetParent()._springing = false
s:GetParent()._expanded = true
end
self.vbar.btnGrip.OnMouseReleased = function(s)
s:GetParent().Dragging = false
s:MouseCapture(false)
if !(s:GetParent():IsHovered() or s:IsHovered()) then
s:GetParent()._hoverExit = CurTime()
end
end
self.vbar.OnMousePressed = function(pnl)
local _, my = pnl:CursorPos()
local gy, gh = pnl.btnGrip.y, pnl.btnGrip:GetTall()
if my < gy then
self:_nudge(-self:GetTall())
elseif my > gy + gh then
self:_nudge(self:GetTall())
end
self.lastInput = CurTime()
self._springing = false
end
function self.vbar:AnimateTo(yPos)
self:GetParent():SetScroll(yPos)
end
function self.vbar:GetScroll()
return self:GetParent():GetScroll()
end
self.padL, self.padT, self.padR, self.padB = 0, 0, 0, 0
self.offset = 0
self.vel = 0
self.drag = false
self.dragLast = 0
self.lastInput = 0
self.scrollStep = 500
self.overscroll = 90
self.overscrollThreshold = 50
self.friction = 8
self.spring = 5
self.dragRes = 0.35
self.gripMin = 28
self.vbarSmooth = 3
self._vb_gripH = nil
self._vb_gripY = nil
self._vb_width = nil
self._needLayout = true
self:SetMouseInputEnabled(true)
self._springing = false
self._springTarget = 0
end
function PANEL:DockPadding(l,t,r,b)
self.padL, self.padT, self.padR, self.padB = l or 0, t or 0, r or 0, b or 0
self:_markDirty()
end
function PANEL:_markDirty()
self._needLayout = true
end
function PANEL:GetCanvas()
return self.content
end
function PANEL:GetVBar()
return self.vbar
end
function PANEL:DisableVBarPadding()
if !IsValid(self.vbar) then return end
self._vbarPadRight = 0
self.vbar:DockMargin(self._vbarPadRight, 0, 0, 0)
self:_markDirty()
self:InvalidateLayout(true)
self.content:InvalidateLayout(true)
end
function PANEL:AddItem(pnl)
pnl:SetParent(self.content)
local old = pnl.OnSizeChanged
pnl.OnSizeChanged = function(...)
if old then pcall(old, ...) end
if IsValid(self) then
self:_markDirty()
self:InvalidateLayout(true)
self.content:InvalidateLayout(true)
self.content:SizeToChildren(false, true)
end
end
self:_markDirty()
return pnl
end
function PANEL:Add(pnl)
return self:AddItem(pnl)
end
function PANEL:OnChildAdded(child)
timer.Simple(0, function()
if child == self.content or child == self.vbar or child == self.vbar.btnGrip then return end
if !IsValid(child) or !IsValid(self) then return end
if child:GetParent() == self then
child:SetParent(self.content)
local old = child.OnSizeChanged
child.OnSizeChanged = function(...)
if old then pcall(old, ...) end
if IsValid(self) then
self:_markDirty()
self:InvalidateLayout(true)
self.content:InvalidateLayout(true)
self.content:SizeToChildren(false, true)
end
end
self:_markDirty()
end
end)
end
function PANEL:Clear()
for _, c in ipairs(self.content:GetChildren()) do c:Remove() end
self.offset = 0
self.vel = 0
self:_markDirty()
end
function PANEL:SetScroll(y)
self.offset = y or 0
end
function PANEL:GetScroll()
return self.offset
end
function PANEL:_range()
if self._needLayout then
local w, h = self:GetWide(), self:GetTall()
local vbw = self.vbar:GetWide()
self.content:DockPadding(0, 0, 0, 0)
local vbReserve = self.vbarReserveWidth or self.vbarExpandedWidth
self.content:SetPos(self.padL, self.padT - self.offset)
local contentW = math.max(0, w - self.padL - self.padR - vbReserve - self._vbarPadRight)
self.content:SetWide(contentW)
self.content:InvalidateLayout(true)
self.content:SizeToChildren(false, true)
local viewH = math.max(0, h - self.padT - self.padB)
local contentH = self.content:GetTall()
if contentH <= viewH then
self.vbar:SetVisible(false)
self.content:SetWide(math.max(0, w - self.padL - self.padR))
self.content:InvalidateLayout(true)
self.content:SizeToChildren(false, true)
contentH = self.content:GetTall()
else
self.vbar:SetVisible(true)
end
self._needLayout = false
end
local viewH = math.max(0, self:GetTall() - self.padT - self.padB)
local contentH = self.content:GetTall()
return math.max(0, contentH - viewH), viewH, contentH
end
function PANEL:_nudge(px)
self.vel = self.vel + px * 10
self.lastInput = CurTime()
end
function PANEL:OnMouseWheeled(delta)
local _, _, contentH = self:_range()
if contentH <= 0 then return end
self._springing = false
self.vel = self.vel - delta * self.scrollStep
self.lastInput = CurTime()
return true
end
function PANEL:OnMousePressed(mc)
if mc != MOUSE_LEFT then return end
local hovered = vgui.GetHoveredPanel()
if IsValid(hovered) and hovered != self and hovered:IsDescendantOf(self.content) then return end
self.drag = true
self.dragLast = select(2, self:CursorPos())
self.vel = 0
self.lastInput = CurTime()
self:MouseCapture(true)
self._springing = false
end
function PANEL:OnMouseReleased(mc)
if mc != MOUSE_LEFT then return end
self.drag = false
self:MouseCapture(false)
local maxScrollDF = select(1, self:_range()) or 0
local extraTop = math.max(0, -self.offset)
local extraBottom = math.max(0, self.offset - maxScrollDF)
if extraTop > self.overscrollThreshold then
self:_startSpring(0)
elseif extraBottom > self.overscrollThreshold then
self:_startSpring(maxScrollDF)
end
end
function PANEL:OnCursorMoved(_, y)
if !self.drag then return end
local dy = y - self.dragLast
self.dragLast = y
local maxScrollDF = self:_range()
local next = self.offset - dy
if next < 0 then
self.offset = self.offset - dy * self.dragRes
elseif next > maxScrollDF then
self.offset = self.offset - dy * self.dragRes
else
self.offset = next
end
self.lastInput = CurTime()
end
function PANEL:SetVBarPaddingRight(enabled)
if !IsValid(self.vbar) then return end
self.vbar:DockMargin(enabled and 6 or 0, 0, 0, 0)
self:_markDirty()
end
function PANEL:PerformLayout(w, h)
self:_markDirty()
end
function PANEL:_startSpring(target)
self._springing = true
self._springTarget = target
self.vel = 0
end
function PANEL:Think()
local ft = FrameTime()
local maxScrollDF, viewH, contentH = self:_range()
local extraTop = math.max(0, -self.offset)
local extraBottom = math.max(0, self.offset - maxScrollDF)
if self._springing then
if CurTime() - self.lastInput < 0.02 then
self._springing = false
end
end
if self._springing then
local t = math.min(1, ft * self.spring)
self.offset = Lerp(t, self.offset, self._springTarget)
self.vel = 0
if math.abs(self.offset - self._springTarget) < 0.5 then
self.offset = self._springTarget
self._springing = false
end
else
if !self.drag then
self.offset = self.offset + self.vel * ft
if self.offset < -self.overscroll then
self.offset = -self.overscroll
self.vel = 0
elseif self.offset > maxScrollDF + self.overscroll then
self.offset = maxScrollDF + self.overscroll
self.vel = 0
else
self.vel = self.vel * math.max(0, 1 - ft * self.friction)
if math.abs(self.vel) < 2 then self.vel = 0 end
end
if CurTime() - self.lastInput > 0.09 and self.vel == 0 then
if extraTop > self.overscrollThreshold then
self:_startSpring(0)
elseif extraBottom > self.overscrollThreshold then
self:_startSpring(maxScrollDF)
end
end
end
end
self.content:SetPos(self.padL, self.padT - math.floor(self.offset))
local vb = self.vbar
if !vb:IsVisible() then return end
local hoveredNow = vb:IsHovered() or vb.btnGrip:IsHovered()
if hoveredNow then
if vb._hoverEnter == 0 then vb._hoverEnter = CurTime() end
vb._hoverExit = 0
else
if vb._hoverExit == 0 then vb._hoverExit = CurTime() end
vb._hoverEnter = 0
end
if vb.Dragging then vb._expanded = true end
if vb._hoverEnter > 0 and CurTime() - vb._hoverEnter >= self.vbarHoverDelay then
vb._expanded = true
end
if vb._hoverExit > 0 and CurTime() - vb._hoverExit >= self.vbarUnhoverDelay and !vb.Dragging then
vb._expanded = false
end
local targetW = (vb._expanded and self.vbarExpandedWidth) or self.vbarDefaultWidth
if vb.Dragging then targetW = self.vbarExpandedWidth end
if self._vb_width == nil then
self._vb_width = targetW
else
self._vb_width = Mantle.func.approachExp(self._vb_width, targetW, self.vbarWidthSpeed, ft)
if math.abs(self._vb_width - targetW) < 0.25 then self._vb_width = targetW end
end
local newW = math.max(1, math.floor(self._vb_width))
if vb:GetWide() != newW then
vb:SetWide(newW)
self:_markDirty()
end
local trackH = vb:GetTall()
local clampedOffset = math.Clamp(self.offset, 0, maxScrollDF)
local ratio = (contentH <= 0) and 1 or math.min(1, viewH / contentH)
local gripH = math.max(self.gripMin, math.floor(trackH * ratio))
local scroll01 = (maxScrollDF <= 0) and 0 or (clampedOffset / maxScrollDF)
local topFrac = math.Clamp(extraTop / self.overscroll, 0, 1)
local bottomFrac = math.Clamp(extraBottom / self.overscroll, 0, 1)
local maxFrac = math.max(topFrac, bottomFrac)
local overscrollFrac = math.Clamp(math.max(extraTop, extraBottom) / self.overscroll, 0, 1)
local gripRatio = gripH / math.max(1, trackH)
local weight = math.Clamp((1 - gripRatio) * 1.5, 0, 1)
local contentToTrack = trackH / math.max(1, contentH)
local extraShift = 0
if extraTop > 0 then extraShift = -extraTop * contentToTrack
elseif extraBottom > 0 then extraShift = extraBottom * contentToTrack end
local proportionalY = (trackH - gripH) * scroll01
local desiredY = proportionalY + extraShift * weight * overscrollFrac
if clampedOffset <= 0.001 then
desiredY = 0
elseif maxScrollDF > 0 and clampedOffset >= maxScrollDF - 0.001 then
desiredY = trackH - gripH
end
local maxShrink = 0.7
local visualGripH = gripH * (1 - maxShrink * overscrollFrac * weight)
visualGripH = math.max(6, visualGripH)
local gripSpeed = 14
if vb.Dragging then
local _, my = vb:CursorPos()
local newY = math.Clamp(my - vb._press_off, 0, trackH - visualGripH)
local s01 = (trackH - visualGripH) <= 0 and 0 or (newY / (trackH - visualGripH))
self.offset = s01 * maxScrollDF
self.vel = 0
self._vb_gripH = visualGripH
self._vb_gripY = newY
else
if self._vb_gripH == nil then
self._vb_gripH = visualGripH
else
self._vb_gripH = Mantle.func.approachExp(self._vb_gripH, visualGripH, gripSpeed, ft)
if math.abs(self._vb_gripH - visualGripH) < 0.25 then self._vb_gripH = visualGripH end
end
if self._vb_gripY == nil then
self._vb_gripY = desiredY
else
local speedY = gripSpeed * (1 + maxFrac * 0.5)
self._vb_gripY = Mantle.func.approachExp(self._vb_gripY, desiredY, speedY, ft)
if math.abs(self._vb_gripY - desiredY) < 0.25 then self._vb_gripY = desiredY end
end
local maxY = math.max(0, trackH - self._vb_gripH)
if self._vb_gripY < 0 then self._vb_gripY = 0 end
if self._vb_gripY > maxY then self._vb_gripY = maxY end
if clampedOffset <= 0.001 then
self._vb_gripY = 0
elseif maxScrollDF > 0 and clampedOffset >= maxScrollDF - 0.001 then
self._vb_gripY = trackH - self._vb_gripH
end
if math.abs(self._vb_gripH - visualGripH) < 0.25 then self._vb_gripH = visualGripH end
if math.abs((self._vb_gripY or 0) - desiredY) < 0.25 then self._vb_gripY = desiredY end
end
local finalH = math.max(1, math.floor(self._vb_gripH))
local finalY = math.floor(math.Clamp(self._vb_gripY or 0, 0, math.max(0, trackH - finalH)))
if clampedOffset <= 0.001 then finalY = 0 end
if maxScrollDF > 0 and clampedOffset >= maxScrollDF - 0.001 then finalY = trackH - finalH end
vb.btnGrip:SetSize(vb:GetWide(), finalH)
vb.btnGrip:SetPos(0, finalY)
end
vgui.Register('MantleScrollPanel', PANEL, 'EditablePanel')

View File

@@ -0,0 +1,206 @@
local PANEL = {}
function PANEL:Init()
self.text = ''
self.min_value = 0
self.max_value = 1
self.decimals = 0
self.convar = nil
self.value = 0
self.smoothPos = 0
self.targetPos = 0
self.dragging = false
self.hover = false
self:SetTall(60)
self.OnValueChanged = function() end
self._convar_last = nil
self._convar_timer = self:CreateConVarSyncTimer()
self._dragAlpha = 255
end
function PANEL:CreateConVarSyncTimer()
local name = 'MantleSlideBoxSync' .. tostring(self)
timer.Create(name, 0.1, 0, function()
if not IsValid(self) or not self.convar then return end
local cvar = GetConVar(self.convar)
if not cvar then return end
local val = cvar:GetFloat()
if self._convar_last ~= val then
self._convar_last = val
self:SetValue(val, true)
end
end)
return name
end
function PANEL:OnRemove()
if self._convar_timer then
timer.Remove(self._convar_timer)
self._convar_timer = nil
end
end
function PANEL:SetRange(min_value, max_value, decimals)
self.min_value = min_value
self.max_value = max_value
self.decimals = decimals or 0
self:SetValue(self.value or min_value)
end
function PANEL:SetConvar(convar)
self.convar = convar
local cvar = GetConVar(convar)
if cvar then
self:SetValue(cvar:GetFloat(), true)
self._convar_last = cvar:GetFloat()
end
end
function PANEL:SetText(text)
self.text = text
end
function PANEL:SetValue(val, fromConVar)
if self.max_value == self.min_value then
val = self.min_value
else
val = math.Clamp(val, self.min_value, self.max_value)
end
if self.decimals > 0 then
val = tonumber(string.format('%.' .. tostring(self.decimals) .. 'f', val)) or val
else
val = math.Round(val)
end
self.value = val
local denom = (self.max_value - self.min_value)
local progress = denom == 0 and 0 or (val - self.min_value) / denom
local w = math.max(0, self:GetWide() - 32)
self.targetPos = math.Clamp(w * progress, 0, w)
if self.convar and not fromConVar then
RunConsoleCommand(self.convar, tostring(val))
self._convar_last = val
end
if self.OnValueChanged then self:OnValueChanged(val) end
end
function PANEL:GetValue()
return self.value
end
function PANEL:UpdateSliderByCursorPos(x)
local w = math.max(0, self:GetWide() - 32)
local progress = math.Clamp(x / w, 0, 1)
local new_value = self.min_value + (progress * (self.max_value - self.min_value))
if self.decimals > 0 then
new_value = tonumber(string.format('%.' .. tostring(self.decimals) .. 'f', new_value))
else
new_value = math.Round(new_value)
end
self:SetValue(new_value)
end
function PANEL:Paint(w, h)
local ft = FrameTime()
local padX = 16
local padTop = 2
local barY = 32
local barH = 6
local barR = barH / 2
local handleW, handleH = 14, 14
local handleR = handleH / 2
local textFont = 'Fated.18'
local minmaxFont = 'Fated.14'
local valueFont = 'Fated.16'
local minmaxPadY = 12
-- Текст сверху
draw.SimpleText(self.text, textFont, padX, padTop, Mantle.color.text)
-- Линия
local barStart = padX + handleW / 2
local barEnd = w - padX - handleW / 2
local barW = math.max(0, barEnd - barStart)
local denom = (self.max_value - self.min_value)
local progress = denom == 0 and 0 or (self.value - self.min_value) / denom
progress = math.Clamp(progress, 0, 1)
local activeW = barW * progress
-- Тень под линией
if Mantle.ui.convar.depth_ui then
RNDX().Rect(barStart, barY, barW, barH)
:Rad(barR)
:Color(Mantle.color.window_shadow)
:Shadow(5, 20)
:Draw()
end
-- Фон линии
RNDX.Draw(barR, barStart, barY, barW, barH, Mantle.color.focus_panel)
RNDX.Draw(barR, barStart, barY, barW, barH, Mantle.color.button_shadow)
-- Активная линия
self.smoothPos = Mantle.func.approachExp(self.smoothPos or 0, activeW, 14, ft)
if math.abs(self.smoothPos - activeW) < 0.5 then self.smoothPos = activeW end
RNDX.Draw(barR, barStart, barY, self.smoothPos, barH, Mantle.color.theme)
local handleX = barStart + self.smoothPos
local handleY = barY + barH / 2
-- Тень под ручкой
RNDX.DrawShadows(handleR, handleX - handleW / 2, handleY - handleH / 2, handleW, handleH, Mantle.color.window_shadow, 3, 10)
local targetAlpha = self.dragging and 100 or 255
self._dragAlpha = Mantle.func.approachExp(self._dragAlpha or 255, targetAlpha, 24, ft)
if math.abs(self._dragAlpha - targetAlpha) < 1 then self._dragAlpha = targetAlpha end
local colorText = Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math.floor(self._dragAlpha))
-- Ручка
RNDX.Draw(handleR, handleX - handleW / 2, handleY - handleH / 2, handleW, handleH, colorText)
-- Значение справа от линии
local valText = (self.decimals > 0) and tostring(self.value) or tostring(self.value)
draw.SimpleText(valText, valueFont, barEnd + handleW / 2 + 4, barY + barH / 2, colorText, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
-- min/max под линией
draw.SimpleText(self.min_value, minmaxFont, barStart, barY + barH + minmaxPadY - 4, Mantle.color.gray, TEXT_ALIGN_LEFT)
draw.SimpleText(self.max_value, minmaxFont, barEnd, barY + barH + minmaxPadY - 4, Mantle.color.gray, TEXT_ALIGN_RIGHT)
end
function PANEL:OnMousePressed(mcode)
if mcode == MOUSE_LEFT then
local x = self:CursorPos()
self:UpdateSliderByCursorPos(x)
self.dragging = true
self:MouseCapture(true)
self.ripple_x = x
self.ripple_anim = 0
self.ripple_active = true
end
end
function PANEL:OnMouseReleased(mcode)
if mcode == MOUSE_LEFT then
self.dragging = false
self:MouseCapture(false)
end
end
function PANEL:OnCursorMoved(x)
if self.dragging then
self:UpdateSliderByCursorPos(x)
end
end
function PANEL:OnCursorEntered()
self.hover = true
end
function PANEL:OnCursorExited()
self.hover = false
end
vgui.Register('MantleSlideBox', PANEL, 'Panel')

View File

@@ -0,0 +1,513 @@
local PANEL = {}
local FrameTime = FrameTime
local CurTime = CurTime
local Lerp = Lerp
local math_max = math.max
local math_floor = math.floor
function PANEL:Init()
self.columns = {}
self.rows = {}
self.headerHeight = 36
self.rowHeight = 32
self.font = 'Fated.18'
self.rowFont = 'Fated.16'
self.selectedRow = nil
self.sortColumn = nil
self.sortDesc = true
self.sortState = 0
self._originalRows = nil
self.hoverAnim = 0
self.padding = 8
self.sidePadding = 12
self.vbarRightPadding = 6
self.vbarLeftExtra = 0
self.header = vgui.Create('Panel', self)
self.header:Dock(TOP)
self.header:SetTall(self.headerHeight)
self.scrollPanel = vgui.Create('MantleScrollPanel', self)
self.scrollPanel:Dock(FILL)
self.scrollPanel:DisableVBarPadding()
self.content = vgui.Create('Panel', self.scrollPanel)
self.content:Dock(TOP)
self.content.Paint = nil
self._rowPanels = {}
self._headerButtons = {}
self._colWidthsTarget = {}
self._colWidthsCurrent = {}
self._lastVBarVis = nil
self._headerPrevActive = {}
self.OnAction = function() end
self.OnRightClick = function() end
self.Think = function()
local dt = FrameTime()
self:UpdateColumnWidthTargets()
for i = 1, #self.columns do
local tgt = self._colWidthsTarget[i] or (self.columns[i] and self.columns[i].width or 100)
self._colWidthsCurrent[i] = Mantle.func.approachExp(self._colWidthsCurrent[i] or tgt, tgt, 20, dt)
end
local leftPad = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0)
local x = leftPad
for i, btn in ipairs(self._headerButtons) do
local w = math_floor(self._colWidthsCurrent[i] or (self.columns[i] and self.columns[i].width or 100))
if IsValid(btn) then
btn:SetSize(w, self.headerHeight)
btn:SetPos(x, 0)
end
x = x + w
end
local total = 0
for i = 1, #self.columns do total = total + (self._colWidthsCurrent[i] or self.columns[i].width) end
local panelW = self:GetWide() or 0
if panelW <= 0 then
local par = self:GetParent()
if IsValid(par) and par.GetWide then panelW = par:GetWide() end
end
if panelW <= 0 then panelW = ScrW() end
local rightPad = self.sidePadding + ((self._lastVBarVis and self.vbarRightPadding) or 0)
local contentW = math_max(total + leftPad + rightPad, panelW)
for _, row in ipairs(self._rowPanels) do
if IsValid(row) and row._labels then
local x2 = leftPad
local hv = row._hoverAlpha or 0
local eased = Mantle.func.easeOutCubic(math.Clamp(hv, 0, 1))
local shift = math_floor(6 * eased)
for i, label in ipairs(row._labels) do
local w = math_floor(self._colWidthsCurrent[i] or (self.columns[i] and self.columns[i].width or 100))
if IsValid(label) then
local dx = 0
if i == 1 then
dx = shift
elseif i == #self.columns then
dx = -shift
end
label:SetSize(w, self.rowHeight)
label:SetPos(x2 + dx, 0)
end
x2 = x2 + w
end
row:SetWide(contentW)
end
end
self.content:SetWide(contentW)
end
end
function PANEL:AddColumn(name, width, align, sortable)
table.insert(self.columns, {
name = name,
width = width or 100,
align = align or TEXT_ALIGN_LEFT,
sortable = sortable or false
})
end
function PANEL:AddItem(...)
local args = {...}
if #args != #self.columns then
print(Mantle.lang.get('mantle', 'table_wrong_args'))
return
end
table.insert(self.rows, args)
self:RebuildRows()
return #self.rows
end
local function getValueType(value)
if value == nil then return 'nil' end
value = tostring(value)
return tonumber(value) and 'number' or 'string'
end
local function compareValues(a, b)
if a == nil and b == nil then return false end
if a == nil then return true end
if b == nil then return false end
local typeA = getValueType(a)
local typeB = getValueType(b)
if typeA != typeB then
return typeA < typeB
end
if typeA == 'number' then
local numA = tonumber(a) or 0
local numB = tonumber(b) or 0
return numA > numB
else
local strA = tostring(a)
local strB = tostring(b)
return strA < strB
end
end
local function cloneRows(tbl)
local out = {}
for i, v in ipairs(tbl) do out[i] = v end
return out
end
function PANEL:SortByColumn(columnIndex)
local column = self.columns[columnIndex]
if !column or !column.sortable then return end
if self.sortColumn != columnIndex then
self.sortColumn = columnIndex
local numCount, total = 0, 0
for _, row in ipairs(self.rows) do
local v = row[columnIndex]
if v != nil then
total = total + 1
if tonumber(tostring(v)) then numCount = numCount + 1 end
end
end
local isNumeric = (total > 0 and numCount >= math.ceil(total / 2))
self.sortDesc = isNumeric
else
self.sortDesc = !self.sortDesc
end
local desc = self.sortDesc
table.sort(self.rows, function(a, b)
local va = a[columnIndex]
local vb = b[columnIndex]
if va == nil and vb == nil then return false end
if va == nil then return !desc end
if vb == nil then return desc end
local sa = tostring(va)
local sb = tostring(vb)
local na = tonumber(sa)
local nb = tonumber(sb)
if na and nb then
if desc then
return na > nb
else
return na < nb
end
end
if na and !nb then
return desc
elseif nb and !na then
return !desc
end
local la = string.lower(sa)
local lb = string.lower(sb)
if desc then
return la > lb
else
return la < lb
end
end)
self:RebuildRows()
end
function PANEL:UpdateColumnWidthTargets()
local cols = self.columns
local n = #cols
if n == 0 then return end
local panelW = self:GetWide() or 0
if (!panelW) or panelW <= 0 then
local parent = self:GetParent()
if IsValid(parent) and parent.GetWide then panelW = parent:GetWide() end
end
if (!panelW) or panelW <= 0 then panelW = ScrW() end
local vbar = (IsValid(self.scrollPanel) and self.scrollPanel.GetVBar) and self.scrollPanel:GetVBar() or nil
local vbarVisible = (IsValid(vbar) and vbar:IsVisible())
local vbarW = (vbarVisible and vbar:GetWide() or 0)
local leftPad = self.sidePadding + (vbarVisible and self.vbarLeftExtra or 0)
local rightPad = self.sidePadding + (vbarVisible and self.vbarRightPadding or 0)
local usable = math_max(0, panelW - leftPad - rightPad - vbarW)
local used = 0
for i = 1, math.max(0, n - 1) do
self._colWidthsTarget[i] = cols[i].width or 100
used = used + self._colWidthsTarget[i]
end
local lastMin = cols[n].width or 100
local remaining = usable - used
if remaining < lastMin then remaining = lastMin end
self._colWidthsTarget[n] = remaining
for i = 1, n do
if self._colWidthsCurrent[i] == nil then
self._colWidthsCurrent[i] = self._colWidthsTarget[i]
end
end
self._lastVBarVis = vbarVisible
end
function PANEL:CreateHeader()
local prev = {}
for i, btn in ipairs(self._headerButtons) do
if IsValid(btn) then prev[i] = btn._activeAlpha or 0 end
end
self.header:Clear()
self._headerButtons = {}
self.header.Paint = function(_, w, h)
RNDX().Rect(0, 0, w, h)
:Radii(16, 16, 0, 0)
:Color(Mantle.color.focus_panel)
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
self:UpdateColumnWidthTargets()
local xPos = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0)
for i, column in ipairs(self.columns) do
local w = math_floor(self._colWidthsCurrent[i] or self._colWidthsTarget[i] or column.width)
local label = vgui.Create('DButton', self.header)
label:SetText('')
label:SetSize(w, self.headerHeight)
label:SetPos(xPos, 0)
label._hover = 0
label._activeAlpha = prev[i] or ((self.sortColumn == i) and 1 or 0)
label.Paint = function(s, bw, bh)
local dt = FrameTime()
local target = s:IsHovered() and 1 or 0
s._hover = Mantle.func.approachExp(s._hover or 0, target, 14, dt)
local activeTarget = (self.sortColumn == i) and 1 or 0
s._activeAlpha = Mantle.func.approachExp(s._activeAlpha or 0, activeTarget, 12, dt)
local tr = Lerp(s._activeAlpha, Mantle.color.text.r, Mantle.color.theme.r)
local tg = Lerp(s._activeAlpha, Mantle.color.text.g, Mantle.color.theme.g)
local tb = Lerp(s._activeAlpha, Mantle.color.text.b, Mantle.color.theme.b)
local ta = Lerp(s._activeAlpha, Mantle.color.text.a, Mantle.color.theme.a)
local textColor = Color(math_floor(tr), math_floor(tg), math_floor(tb), math_floor(ta))
draw.SimpleText(column.name, self.font, bw/2, bh/2, textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
if column.sortable then
label.DoClick = function()
self:SortByColumn(i)
Mantle.func.sound()
end
end
table.insert(self._headerButtons, label)
xPos = xPos + w
end
end
function PANEL:CreateRow(rowIndex, rowData)
local row = vgui.Create('DButton', self.content)
row:Dock(TOP)
row:DockMargin(0, 0, 0, 1)
row:SetTall(self.rowHeight)
row:SetText('')
row._index = rowIndex
row._hoverAlpha = 0
row._selectedAlpha = 0
row._labels = {}
row.Paint = function(s, w, h)
local dt = FrameTime()
local hoverTarget = s:IsHovered() and 1 or 0
s._hoverAlpha = Mantle.func.approachExp(s._hoverAlpha, hoverTarget, 18, dt)
local selTarget = (self.selectedRow == s._index) and 1 or 0
s._selectedAlpha = Mantle.func.approachExp(s._selectedAlpha, selTarget, 22, dt)
local base = Mantle.color.panel_alpha[1]
local hoverCol = Mantle.color.hover
local selCol = Mantle.color.theme
local mixHover = s._hoverAlpha * (1 - s._selectedAlpha)
local blendA = s._selectedAlpha * 0.9 + mixHover * 0.35
local r = Lerp(blendA, base.r, selCol.r)
local g = Lerp(blendA, base.g, selCol.g)
local b = Lerp(blendA, base.b, selCol.b)
local a = Lerp(blendA, base.a, selCol.a)
if s._hoverAlpha > 0.01 and s._selectedAlpha < 0.9 then
local hoverR = Lerp(s._hoverAlpha * 0.6, r, hoverCol.r)
local hoverG = Lerp(s._hoverAlpha * 0.6, g, hoverCol.g)
local hoverB = Lerp(s._hoverAlpha * 0.6, b, hoverCol.b)
r,g,b = hoverR, hoverG, hoverB
end
RNDX().Rect(0, 0, w, math.max(0, h - 1))
:Color(Color(math.floor(r), math.floor(g), math.floor(b), math.floor(a)))
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
row.DoClick = function()
self.selectedRow = rowIndex
self._keyboardIndex = rowIndex
self.OnAction(rowData)
Mantle.func.sound()
end
row.DoRightClick = function()
self.selectedRow = rowIndex
self.OnRightClick(rowData)
local menu = Mantle.ui.derma_menu()
for i, column in ipairs(self.columns) do
menu:AddOption(Mantle.lang.get('mantle', 'table_copy') .. ' ' .. column.name, function()
SetClipboardText(tostring(rowData[i]))
end)
end
menu:AddSpacer()
menu:AddOption(Mantle.lang.get('mantle', 'table_delete_row'), function()
self:RemoveRow(rowIndex)
end, 'icon16/delete.png')
end
local leftPad = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0)
local xPos = leftPad
for i, column in ipairs(self.columns) do
local w = math_floor(self._colWidthsCurrent[i] or self._colWidthsTarget[i] or column.width)
local label = vgui.Create('DLabel', row)
label:SetText(tostring(rowData[i]))
label:SetFont(self.rowFont)
label:SetTextColor(Mantle.color.text)
label:SetSize(w, self.rowHeight)
label:SetPos(xPos, 0)
if column.align == TEXT_ALIGN_LEFT then
label:SetTextInset(self.padding, 0)
label:SetContentAlignment(4)
elseif column.align == TEXT_ALIGN_RIGHT then
label:SetTextInset(0, 0)
label:SetContentAlignment(6)
else
label:SetTextInset(0, 0)
label:SetContentAlignment(5)
end
table.insert(row._labels, label)
xPos = xPos + w
end
table.insert(self._rowPanels, row)
end
function PANEL:RebuildRows()
local savedRowHover = {}
for idx, oldRow in ipairs(self._rowPanels) do
if IsValid(oldRow) then
savedRowHover[idx] = oldRow._hoverAlpha or 0
end
end
local prevHeader = {}
for i, btn in ipairs(self._headerButtons) do
if IsValid(btn) then prevHeader[i] = btn._activeAlpha or 0 end
end
self.content:Clear()
self._rowPanels = {}
self._headerButtons = {}
self:UpdateColumnWidthTargets()
self:CreateHeader()
for rowIndex, rowData in ipairs(self.rows) do
self:CreateRow(rowIndex, rowData)
if savedRowHover[rowIndex] and IsValid(self._rowPanels[#self._rowPanels]) then
self._rowPanels[#self._rowPanels]._hoverAlpha = savedRowHover[rowIndex]
end
end
local total = 0
for i = 1, #self.columns do total = total + (self._colWidthsTarget[i] or self.columns[i].width) end
local panelW = self:GetWide() or 0
if panelW <= 0 then
local parent = self:GetParent()
if IsValid(parent) and parent.GetWide then panelW = parent:GetWide() end
end
if panelW <= 0 then panelW = ScrW() end
local leftPad = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0)
local rightPad = self.sidePadding + ((self._lastVBarVis and self.vbarRightPadding) or 0)
local contentW = math_max(total + leftPad + rightPad, panelW)
self.content:SetSize(contentW, #self.rows * (self.rowHeight + 1))
self.scrollPanel:InvalidateLayout(true)
end
function PANEL:SetAction(func)
self.OnAction = func
end
function PANEL:SetRightClickAction(func)
self.OnRightClick = func
end
function PANEL:Clear()
self.rows = {}
self.selectedRow = nil
self.content:Clear()
end
function PANEL:GetSelectedRow()
return self.selectedRow and self.rows[self.selectedRow] or nil
end
function PANEL:GetRowCount()
return #self.rows
end
function PANEL:RemoveRow(index)
if index and index > 0 and index <= #self.rows then
table.remove(self.rows, index)
if self.selectedRow == index then
self.selectedRow = nil
elseif self.selectedRow and self.selectedRow > index then
self.selectedRow = self.selectedRow - 1
end
self:RebuildRows()
self.scrollPanel:InvalidateLayout(true)
end
end
function PANEL:Paint(w, h)
RNDX().Rect(0, 0, w, h)
:Rad(16)
:Color(Mantle.color.panel_alpha[2])
:Shape(RNDX.SHAPE_IOS)
:Draw()
end
vgui.Register('MantleTable', PANEL, 'Panel')

View File

@@ -0,0 +1,196 @@
local PANEL = {}
function PANEL:Init()
self.tabs = {}
self.active_id = 1
self.tab_height = 38
self.animation_speed = 12
self.tab_style = 'modern' -- modern или classic
self.indicator_height = 2
self.indicator_x = 0
self.indicator_w = 0
self.indicator_target_x = 0
self.indicator_target_w = 0
self.panel_tabs = vgui.Create('Panel', self)
self.panel_tabs.Paint = nil
self.content = vgui.Create('Panel', self)
self.content.Paint = nil
end
function PANEL:Think()
if self.tab_style == 'modern' then
self.indicator_x = Mantle.func.approachExp(self.indicator_x, self.indicator_target_x, self.animation_speed, FrameTime())
self.indicator_w = Mantle.func.approachExp(self.indicator_w, self.indicator_target_w, self.animation_speed, FrameTime())
if math.abs(self.indicator_x - self.indicator_target_x) < 0.5 then
self.indicator_x = self.indicator_target_x
end
if math.abs(self.indicator_w - self.indicator_target_w) < 0.5 then
self.indicator_w = self.indicator_target_w
end
end
end
function PANEL:SetTabStyle(style)
self.tab_style = style
self:Rebuild()
end
function PANEL:SetTabHeight(height)
self.tab_height = height
self:Rebuild()
end
function PANEL:SetIndicatorHeight(height)
self.indicator_height = height
self:Rebuild()
end
function PANEL:AddTab(name, pan, icon)
local newId = #self.tabs + 1
self.tabs[newId] = {
name = name,
pan = pan,
icon = icon
}
self.tabs[newId].pan:SetParent(self.content)
self.tabs[newId].pan:Dock(FILL)
self.tabs[newId].pan:SetVisible(newId == 1 and true or false)
self:Rebuild()
end
local color_btn_hovered = Color(255, 255, 255, 10)
function PANEL:Rebuild()
self.panel_tabs:Clear()
for id, tab in ipairs(self.tabs) do
local btnTab = vgui.Create('Button', self.panel_tabs)
tab._btn = btnTab
if self.tab_style == 'modern' then
surface.SetFont('Fated.18')
local textW = select(1, surface.GetTextSize(tab.name))
local iconW = tab.icon and 16 or 0
local iconTextGap = tab.icon and 8 or 0
local padding = 16
local btnWidth = padding + iconW + iconTextGap + textW + padding
btnTab:Dock(LEFT)
btnTab:DockMargin(0, 0, 6, 0)
btnTab:SetTall(34)
btnTab:SetWide(btnWidth)
else
btnTab:Dock(TOP)
btnTab:DockMargin(0, 0, 0, 6)
btnTab:SetTall(34)
end
btnTab:SetText('')
btnTab.DoClick = function()
self.tabs[self.active_id].pan:SetVisible(false)
tab.pan:SetVisible(true)
self.active_id = id
if self.tab_style == 'modern' and tab._btn then
self.indicator_target_x = tab._btn:GetX()
self.indicator_target_w = tab._btn:GetWide()
end
Mantle.func.sound()
end
btnTab.DoRightClick = function()
local dm = Mantle.ui.derma_menu()
for k, tab in pairs(self.tabs) do
dm:AddOption(tab.name, function()
self.tabs[self.active_id].pan:SetVisible(false)
tab.pan:SetVisible(true)
self.active_id = k
if self.tab_style == 'modern' and tab._btn then
self.indicator_target_x = tab._btn:GetX()
self.indicator_target_w = tab._btn:GetWide()
end
end, tab.icon)
end
end
btnTab.Paint = function(s, w, h)
local isActive = self.active_id == id
local colorText = isActive and Mantle.color.theme or Mantle.color.text
local colorIcon = isActive and Mantle.color.theme or color_white
if self.tab_style == 'modern' then
if s:IsHovered() then
RNDX.Draw(16, 0, 0, w, h, color_btn_hovered, RNDX.SHAPE_IOS + (isActive and RNDX.NO_BL + RNDX.NO_BR or 0))
end
local padding = 16
local iconW = tab.icon and 16 or 0
local iconTextGap = tab.icon and 8 or 0
local textX = padding + (iconW > 0 and (iconW + iconTextGap) or 0)
if tab.icon then
RNDX.DrawMaterial(0, padding, (h - 16) * 0.5, 16, 16, colorIcon, tab.icon)
end
draw.SimpleText(tab.name, 'Fated.18', textX, h * 0.5, colorText, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
else
if s:IsHovered() then
RNDX.Draw(24, 0, 0, w, h, color_btn_hovered, RNDX.SHAPE_IOS)
end
draw.SimpleText(tab.name, 'Fated.18', 34, h * 0.5 - 1, colorText, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
if tab.icon then
RNDX.DrawMaterial(0, 9, 9, 16, 16, colorIcon, tab.icon)
else
RNDX.Draw(24, 9, 9, 16, 16, colorIcon, RNDX.SHAPE_IOS)
end
end
end
end
self.panel_tabs.Paint = function(s, w, h)
if self.tab_style == 'modern' and self.indicator_w > 0 then
RNDX.Draw(0, self.indicator_x, h - self.indicator_height, self.indicator_w, self.indicator_height, Mantle.color.theme)
end
end
end
function PANEL:PerformLayout(w, h)
if self.tab_style == 'modern' then
self.panel_tabs:Dock(TOP)
self.panel_tabs:DockMargin(0, 0, 0, 4)
self.panel_tabs:SetTall(self.tab_height)
else
self.panel_tabs:Dock(LEFT)
self.panel_tabs:DockMargin(0, 0, 4, 0)
self.panel_tabs:SetWide(190)
end
self.content:Dock(FILL)
if self.tab_style == 'modern' then
local activeBtn = nil
if self.tabs[self.active_id] then
activeBtn = self.tabs[self.active_id]._btn
end
if IsValid(activeBtn) then
local bx, by = activeBtn:GetPos()
local bw, bh = activeBtn:GetSize()
self.indicator_target_x = bx
self.indicator_target_w = bw
if self.indicator_w == 0 and self.indicator_x == 0 then
self.indicator_x = self.indicator_target_x
self.indicator_w = self.indicator_target_w
end
else
self.indicator_target_x = 0
self.indicator_target_w = 0
end
end
end
vgui.Register('MantleTabs', PANEL, 'Panel')

View File

@@ -0,0 +1,257 @@
local PANEL = {}
local function utf8_iter(s)
return s:gmatch('([%z\1-\127\194-\244][\128-\191]*)')
end
function PANEL:Init()
self.text = ''
self.font = 'Fated.18'
self.color = Mantle.color.text
self.align = TEXT_ALIGN_LEFT
self.valign = 'top'
self.padding = 6
self._lines = {''}
self._line_h = 16
self._last_w, self._last_h = 0, 0
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
end
function PANEL:SetText(text)
self.text = text
self:InvalidateLayout()
end
function PANEL:GetText()
return self.text
end
function PANEL:SetFont(font)
self.font = font
self:InvalidateLayout()
end
function PANEL:SetColor(col)
self.color = col
self:InvalidateLayout()
end
function PANEL:SetAlign(a)
self.align = a
self:InvalidateLayout()
end
function PANEL:SetVAlign(v)
if v == 'top' or v == 'center' or v == 'bottom' then
self.valign = v
self:InvalidateLayout()
end
end
function PANEL:SetPadding(p)
self.padding = p
self:InvalidateLayout()
end
local function GetTextSize(font, txt)
surface.SetFont(font)
local ok, w, h = pcall(surface.GetTextSize, txt)
if not ok then return 0, 16 end
if not h or type(h) != 'number' or h <= 0 then
local ok2, _, h2 = pcall(surface.GetTextSize, 'Ay')
if ok2 and type(h2) == 'number' and h2 > 0 then
h = h2
else
h = 16
end
end
return tonumber(w) or 0, h
end
local function WrapAndEllipsize(text, font, maxw, max_lines)
if maxw <= 0 or max_lines <= 0 then
return {''}, true
end
local ell = '...'
local ell_w = GetTextSize(font, ell)
local paragraphs = string.Explode('\n', text)
local lines = {}
local truncated = false
for pi = 1, #paragraphs do
local para = paragraphs[pi]
if para == '' then
table.insert(lines, '')
if #lines >= max_lines then
truncated = (pi < #paragraphs)
break
end
else
local words = string.Explode(' ', para)
local i = 1
local cur = ''
while i <= #words do
local w = words[i]
local test = (cur == '') and w or (cur .. ' ' .. w)
local tw = GetTextSize(font, test)
if tw <= maxw then
cur = test
i = i + 1
else
if cur != '' then
table.insert(lines, cur)
cur = ''
if #lines >= max_lines then break end
else
local part = ''
for ch in utf8_iter(w) do
local tpart = part .. ch
local tw2 = GetTextSize(font, tpart)
if tw2 <= maxw then
part = tpart
else
if part == '' then
local ch_w = GetTextSize(font, ch)
if ch_w <= maxw then
table.insert(lines, ch)
else
table.insert(lines, ch)
end
else
table.insert(lines, part)
local ch_w = GetTextSize(font, ch)
if ch_w <= maxw then
part = ch
else
table.insert(lines, ch)
part = ''
end
end
part = part or ''
if #lines >= max_lines then break end
end
end
if #lines >= max_lines then break end
cur = part
i = i + 1
end
end
end
if #lines < max_lines and cur != '' then
table.insert(lines, cur)
end
if #lines >= max_lines then
if i <= #words or pi < #paragraphs then
truncated = true
end
if truncated then
local rest = ''
if i <= #words then
for j = i, #words do
rest = rest .. words[j] .. (j < #words and ' ' or '')
end
end
if pi < #paragraphs then
for pj = pi + 1, #paragraphs do
if paragraphs[pj] != '' then
rest = rest .. (rest != '' and ' ' or '') .. paragraphs[pj]
end
end
end
if #lines == 0 then
lines[1] = ''
end
local lastIdx = #lines
local prev = lines[lastIdx] or ''
if rest == '' then
rest = prev
else
rest = prev .. (rest != '' and (' ' .. rest) or '')
end
local res = ''
for ch in utf8_iter(rest) do
local t = res .. ch
local tw = GetTextSize(font, t)
if tw + ell_w <= maxw then
res = t
else
break
end
end
lines[lastIdx] = (res == '' and ell) or (res .. ell)
end
break
end
end
end
if #lines == 0 then lines[1] = '' end
return lines, truncated
end
function PANEL:_rebuild_if_needed()
local w, h = self:GetSize()
if w == self._last_w and h == self._last_h then return end
self._last_w, self._last_h = w, h
local avail_w_df = math.max(1, w - self.padding * 2)
local _, line_h = GetTextSize(self.font, 'Ay')
self._line_h = line_h or 16
local max_lines = math.max(1, math.floor((h - self.padding * 2) / self._line_h))
local lines, trunc = WrapAndEllipsize(self.text, self.font, avail_w_df, max_lines)
self._lines = lines
self._truncated = trunc
end
function PANEL:PerformLayout(w, h)
self:_rebuild_if_needed()
end
function PANEL:Paint(w, h)
self:_rebuild_if_needed()
local lines = self._lines or {''}
local line_h = self._line_h or 16
local total_h = #lines * line_h
local start_y = self.padding
if self.valign == 'center' then
start_y = math.floor((h - total_h) / 2)
elseif self.valign == 'bottom' then
start_y = h - self.padding - total_h
end
surface.SetFont(self.font)
for i = 1, #lines do
local line = lines[i]
local y = start_y + (i - 1) * line_h
local x = self.padding
if self.align == TEXT_ALIGN_CENTER then
x = w * 0.5
elseif self.align == TEXT_ALIGN_RIGHT then
x = w - self.padding
end
draw.SimpleText(line, self.font, x, y, self.color, self.align, TEXT_ALIGN_TOP)
end
return true
end
vgui.Register('MantleText', PANEL, 'EditablePanel')

View File

@@ -0,0 +1,35 @@
local color_accept = Color(35, 103, 51)
function Mantle.ui.text_box(title, desc, func)
Mantle.ui.menu_text_box = vgui.Create('MantleFrame')
Mantle.ui.menu_text_box:SetSize(300, 134)
Mantle.ui.menu_text_box:Center()
Mantle.ui.menu_text_box:MakePopup()
Mantle.ui.menu_text_box:SetTitle(title)
Mantle.func.animate_appearance(Mantle.ui.menu_text_box, Mantle.ui.menu_text_box:GetWide(), Mantle.ui.menu_text_box:GetTall(), 0.3, 0.2, nil, 0.9)
Mantle.ui.menu_text_box:DockPadding(12, 30, 12, 12)
local entry = vgui.Create('MantleEntry', Mantle.ui.menu_text_box)
entry:Dock(TOP)
entry:SetTitle(desc)
local function apply_func()
func(entry:GetValue())
Mantle.ui.menu_text_box:Remove()
end
entry.OnEnter = function()
apply_func()
end
local btn_accept = vgui.Create('MantleBtn', Mantle.ui.menu_text_box)
btn_accept:Dock(BOTTOM)
btn_accept:SetTall(30)
btn_accept:SetTxt(Mantle.lang.get('mantle', 'apply'))
btn_accept:SetColorHover(color_accept)
btn_accept.DoClick = function()
Mantle.func.sound()
apply_func()
end
end

View File

@@ -0,0 +1,82 @@
--[[
* Mantle *
GitHub: https://github.com/darkfated/mantle
Author's telegram: @darkfated
]]--
local function RunScripts()
Mantle.run_cl('config/colors.lua')
Mantle.run_cl('core/func.lua')
Mantle.run_cl('core/vgui.lua')
Mantle.run_cl('core/legacy_vgui.lua')
Mantle.run_cl('core/menu.lua')
Mantle.run_cl('core/vgui_elements/button.lua')
Mantle.run_cl('core/vgui_elements/checkbox.lua')
Mantle.run_cl('core/vgui_elements/color_picker.lua')
Mantle.run_cl('core/vgui_elements/derma_menu.lua')
Mantle.run_cl('core/vgui_elements/entry.lua')
Mantle.run_cl('core/vgui_elements/frame.lua')
Mantle.run_cl('core/vgui_elements/player_selector.lua')
Mantle.run_cl('core/vgui_elements/radialpanel.lua')
Mantle.run_cl('core/vgui_elements/scrollpanel.lua')
Mantle.run_cl('core/vgui_elements/slidebox.lua')
Mantle.run_cl('core/vgui_elements/tabs.lua')
Mantle.run_cl('core/vgui_elements/textbox.lua')
Mantle.run_cl('core/vgui_elements/category.lua')
Mantle.run_cl('core/vgui_elements/combobox.lua')
Mantle.run_cl('core/vgui_elements/table.lua')
Mantle.run_cl('core/vgui_elements/text.lua')
Mantle.run_cl('modules/shadows.lua')
Mantle.run_cl('modules/material_url.lua')
Mantle.run_sh('modules/notify.lua')
Mantle.run_sh('modules/utf8.lua')
end
local function RunAddons()
local _, addonsName = file.Find('mantle_addons/*', 'LUA')
for _, addon in ipairs(addonsName) do
if file.Exists('mantle_addons/' .. addon .. '/init.lua', 'LUA') then
Mantle.run_sh('mantle_addons/' .. addon .. '/init.lua')
end
if file.Exists('mantle_addons/' .. addon .. '/lang.lua', 'LUA') then
local lang = Mantle.run_sh('mantle_addons/' .. addon .. '/lang.lua')
Mantle.lang.list[addon] = lang
end
end
Mantle.run_sh('core/lang.lua')
end
local function InitLib()
if SERVER then
resource.AddWorkshop('2924839375') -- DarkFated font
resource.AddWorkshop('3126986993') -- Mantle
end
local color_div = Color(168, 109, 236)
MsgC(color_white, '------------------\n')
MsgC(Color(0, 255, 0), '| Mantle LIBRARY |\n')
MsgC(color_white, '------------------\n')
Mantle = Mantle or {
lang = { list = {}, default = 'en' },
}
Mantle.run_cl = SERVER and AddCSLuaFile or include
Mantle.run_sv = SERVER and include or function() end
Mantle.run_sh = function(f)
local client = Mantle.run_cl(f)
local server = Mantle.run_sv(f)
return SERVER and server or client
end
RunScripts()
RunAddons()
end
InitLib()

View File

@@ -0,0 +1,40 @@
local file, Mat, Fetch, find = file, Material, http.Fetch, string.find
local errorMat = Mat('error')
local WebImageCache = {}
--[[
Функция для скачивания материала по ссылке и его кэшированного использования
]]--
function http.DownloadMaterial(url, path, callback, retry_count)
if WebImageCache[url] then
return callback(WebImageCache[url])
end
local dataPath = 'data/' .. path
if file.Exists(path, 'DATA') then
WebImageCache[url] = Mat(dataPath, 'noclamp mips')
callback(WebImageCache[url])
else
Fetch(url, function(img)
if !img or find(img, '<!DOCTYPE HTML>', 1, true) then
return callback(errorMat)
end
file.Write(path, img)
WebImageCache[url] = Mat(dataPath, 'noclamp mips')
callback(WebImageCache[url])
end, function()
if retry_count and retry_count > 0 then
retry_count = retry_count - 1
http.DownloadMaterial(url, path, callback, retry_count)
else
callback(errorMat)
end
end)
end
end

View File

@@ -0,0 +1,25 @@
if SERVER then
util.AddNetworkString('Mantle-Notify')
--[[
Функция для выведения в чат текста.
Можно выводить определённой цели информацию, либо всем, указав вместо pl - true
]]--
function Mantle.notify(pl, header_color, header, text)
net.Start('Mantle-Notify')
net.WriteString(header)
net.WriteColor(header_color)
net.WriteString(text)
if pl == true then net.Broadcast() else net.Send(pl) end
end
else
net.Receive('Mantle-Notify', function()
local headerText = net.ReadString()
local headerColor = net.ReadColor()
local headerColorDop = Color(headerColor.r + 10, headerColor.g + 10, headerColor.b + 10)
local text = net.ReadString()
chat.AddText(headerColorDop, '[', headerColor, headerText, headerColorDop, '] ', color_white, text)
chat.PlaySound()
end)
end

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,115 @@
local math_sin = math.sin
local math_cos = math.cos
local math_rad = math.rad
local math_ceil = math.ceil
local function CreateBShadows()
BShadows = {}
local resStr = Mantle.func.sw .. Mantle.func.sh
BShadows.RenderTarget = GetRenderTarget('BShadows_original_' .. resStr, Mantle.func.sw, Mantle.func.sh)
BShadows.RenderTarget2 = GetRenderTarget('BShadows_shadow_' .. resStr, Mantle.func.sw, Mantle.func.sh)
BShadows.ShadowMaterial = CreateMaterial('BShadows', 'UnlitGeneric', {
['$translucent'] = 1,
['$vertexalpha'] = 1,
['alpha'] = 1
})
BShadows.ShadowMaterialGrayscale = CreateMaterial('BShadows_grayscale', 'UnlitGeneric', {
['$translucent'] = 1,
['$vertexalpha'] = 1,
['$alpha'] = 1,
['$color'] = '0 0 0',
['$color2'] = '0 0 0'
})
BShadows.BeginShadow = function()
render.PushRenderTarget(BShadows.RenderTarget)
render.OverrideAlphaWriteEnable(true, true)
render.Clear(0, 0, 0, 0)
render.OverrideAlphaWriteEnable(false, false)
cam.Start2D()
end
BShadows.EndShadow = function(intensity, spread, blur, opacity, direction, distance, bool_shadow_only)
opacity = opacity or 255
direction = direction or 0
distance = distance or 0
bool_shadow_only = bool_shadow_only or false
render.CopyRenderTargetToTexture(BShadows.RenderTarget2)
if blur > 0 then
render.OverrideAlphaWriteEnable(true, true)
render.BlurRenderTarget(BShadows.RenderTarget2, spread, spread, blur)
render.OverrideAlphaWriteEnable(false, false)
end
render.PopRenderTarget()
BShadows.ShadowMaterial:SetTexture('$basetexture', BShadows.RenderTarget)
BShadows.ShadowMaterialGrayscale:SetTexture('$basetexture', BShadows.RenderTarget2)
local xOffset = math_sin(math_rad(direction)) * distance
local yOffset = math_cos(math_rad(direction)) * distance
BShadows.ShadowMaterialGrayscale:SetFloat('$alpha', opacity / 255)
render.SetMaterial(BShadows.ShadowMaterialGrayscale)
for i = 1, math_ceil(intensity) do
render.DrawScreenQuadEx(xOffset, yOffset, Mantle.func.sw, Mantle.func.sh)
end
if !bool_shadow_only then
BShadows.ShadowMaterial:SetTexture('$basetexture', BShadows.RenderTarget)
render.SetMaterial(BShadows.ShadowMaterial)
render.DrawScreenQuad()
end
cam.End2D()
end
BShadows.DrawShadowTexture = function(texture, intensity, spread, blur, opacity, direction, distance, bool_shadow_only)
opacity = opacity or 255
direction = direction or 0
distance = distance or 0
bool_shadow_only = bool_shadow_only or false
render.CopyTexture(texture, BShadows.RenderTarget2)
if blur > 0 then
render.PushRenderTarget(BShadows.RenderTarget2)
render.OverrideAlphaWriteEnable(true, true)
render.BlurRenderTarget(BShadows.RenderTarget2, spread, spread, blur)
render.OverrideAlphaWriteEnable(false, false)
render.PopRenderTarget()
end
BShadows.ShadowMaterialGrayscale:SetTexture('$basetexture', BShadows.RenderTarget2)
local xOffset = math_sin(math_rad(direction)) * distance
local yOffset = math_cos(math_rad(direction)) * distance
BShadows.ShadowMaterialGrayscale:SetFloat('$alpha', opacity / 255)
render.SetMaterial(BShadows.ShadowMaterialGrayscale)
for i = 1, math_ceil(intensity) do
render.DrawScreenQuadEx(xOffset, yOffset, Mantle.func.sw, Mantle.func.sh)
end
if !bool_shadow_only then
BShadows.ShadowMaterial:SetTexture('$basetexture', texture)
render.SetMaterial(BShadows.ShadowMaterial)
render.DrawScreenQuad()
end
end
end
CreateBShadows()
hook.Add('OnScreenSizeChanged', 'Mantle.Shadows', function()
CreateBShadows()
end)

View File

@@ -0,0 +1,23 @@
if utf8 == nil then
utf8 = {
charpattern = '[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*'
}
end
local uc_lc = {['А']='а',['Б']='б',['В']='в',['Г']='г',['Д']='д',['Е']='е',['Ё']='ё',['Ж']='ж',['З']='з',['И']='и',['Й']='й',['К']='к',['Л']='л',['М']='м',['Н']='н',['О']='о',['П']='п',['Р']='р',['С']='с',['Т']='т',['У']='у',['Ф']='ф',['Х']='х',['Ц']='ц',['Ч']='ч',['Ш']='ш',['Щ']='щ',['Ъ']='ъ',['Ы']='ы',['Ь']='ь',['Э']='э',['Ю']='ю',['Я']='я'}
local lc_uc = {}
for uc, lc in pairs(uc_lc) do
lc_uc[lc] = uc
end
setmetatable(uc_lc, {__index = function(_, char) return char:lower() end})
setmetatable(lc_uc, {__index = function(_, char) return char:upper() end})
function utf8.lower(text)
return text:gsub(utf8.charpattern, uc_lc)
end
function utf8.upper(text)
return text:gsub(utf8.charpattern, lc_uc)
end