├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── glualint.json ├── lua ├── autorun │ └── sh_melon_lib_init.lua └── melon │ ├── core │ ├── data │ │ ├── sh_net.lua │ │ ├── sh_net_schema.lua │ │ ├── sh_quick_serialize.lua │ │ ├── sh_stream.lua │ │ ├── sh_table.lua │ │ ├── sh_url_sanitize.lua │ │ └── sql │ │ │ ├── sv_adapters.lua │ │ │ ├── sv_connection.lua │ │ │ └── sv_parsers.lua │ ├── drawing │ │ ├── cl_blur.lua │ │ ├── cl_gradients.lua │ │ ├── cl_images.lua │ │ ├── cl_matcache.lua │ │ ├── cl_noise.lua │ │ ├── cl_panel_dev_suite.lua │ │ ├── cl_scaling.lua │ │ ├── cl_stencil.lua │ │ ├── cl_superellipse.lua │ │ ├── cl_text.lua │ │ ├── cl_vgui.lua │ │ ├── sh_colors.lua │ │ └── sh_fonts.lua │ ├── elements │ │ ├── cl_button.lua │ │ ├── cl_draggable.lua │ │ ├── cl_mask.lua │ │ ├── cl_resizeable.lua │ │ ├── cl_tabs.lua │ │ ├── cl_text_entry.lua │ │ ├── panel_suite │ │ │ ├── cl_ps_main.lua │ │ │ ├── cl_ps_nav.lua │ │ │ ├── cl_ps_tools.lua │ │ │ ├── cl_ps_topbar.lua │ │ │ ├── cl_ps_tree.lua │ │ │ └── tabs │ │ │ │ ├── cl_pst_elements.lua │ │ │ │ ├── cl_pst_renderview.lua │ │ │ │ └── cl_pst_settings.lua │ │ └── scroller │ │ │ ├── cl_scrollbar.lua │ │ │ └── cl_scrollpanel.lua │ ├── extensions │ │ ├── cl_panel.lua │ │ ├── cl_panel_extensions.lua │ │ └── panel │ │ │ ├── cl_pext_hook.lua │ │ │ └── cl_pext_net.lua │ ├── misc │ │ ├── format │ │ │ ├── sh_filters.lua │ │ │ └── sh_stringformat.lua │ │ ├── lua │ │ │ ├── builder │ │ │ │ ├── sh_chunk.lua │ │ │ │ ├── sh_expr.lua │ │ │ │ ├── sh_kinds.lua │ │ │ │ └── vis │ │ │ │ │ └── sh_kind_printers.lua │ │ │ └── sh_lexer.lua │ │ ├── sh_grid.lua │ │ ├── sh_math.lua │ │ ├── sh_modules.lua │ │ ├── sh_promise.lua │ │ ├── sv_clearcmd.lua │ │ └── sv_update.lua │ └── thirdparty │ │ ├── cl_circles.lua │ │ ├── cl_melons_masks.lua │ │ ├── cl_melons_rounded_boxes.lua │ │ ├── cl_melons_shadows.lua │ │ ├── cl_ui3d2d.lua │ │ ├── credit.txt │ │ ├── material-avatar │ │ ├── cl_material-avatar.lua │ │ └── cl_vgui-element.lua │ │ └── sh_uuid.lua │ ├── modules │ └── template │ │ ├── __init__.lua │ │ ├── config │ │ └── sh_config.lua │ │ └── src │ │ └── .gitkeep │ └── preload │ ├── sh_accessors.lua │ ├── sh_debug.lua │ ├── sh_delay_http.lua │ ├── sh_files.lua │ ├── sh_function_attributes.lua │ ├── sh_index_behavior.lua │ ├── sh_logging.lua │ ├── sh_postload.lua │ ├── sh_profile.lua │ └── sh_types.lua └── resource └── fonts └── poppins_melon_lib.ttf /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doctest.lua 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 MustBeLeaving 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://i.imgur.com/4tO48eh.png) 2 | 3 | MelonLib is a multipurpose Garry's Mod development library that includes a plethora of different utilities. 4 | 5 | > Documentation can be found above the function declarations, formal documentation is coming soon:tm: 6 | 7 | # Includes 8 | - Tons of development utilities 9 | - Panel Debugger 10 | - Meta-File Interfacing 11 | - Function Attribute System 12 | - Message Logger 13 | - Advanced String Formatting with Filters 14 | - Font System 15 | - Color Manipulation Functions 16 | - Image/Material Caching and Downloading Handler 17 | - Complete Module Loading System 18 | - Many third-party libraries 19 | - [Material Avatar](https://github.com/WilliamVenner/glua-material-avatar) 20 | - [Circles!](https://github.com/SneakySquid/Circles) 21 | - [UI3d2d](https://github.com/TomDotBat/ui3d2d) 22 | - And lots more little utilities! 23 | 24 | # Examples 25 | 26 | String Formatting System: 27 | ```lua 28 | melon.string.print("Did you know that {name | capitalize()} are {adjective}?", { 29 | name = "you", 30 | adjective = "bald" 31 | }) 32 | -- Did you know that You are bald? 33 | 34 | melon.string.print("you are {1}, {2} and {3.1}", 35 | "fat", 36 | "weird", 37 | { 38 | "smelly" 39 | }) 40 | -- you are fat, weird and smelly 41 | 42 | melon.string.print("Blah blah blah {1:Nick|call($1)}", LocalPlayer()) 43 | -- Blah blah blah Melon 44 | ``` 45 | 46 | Font System: 47 | ```lua 48 | draw.Text({ 49 | text = "Some Text", 50 | pos = {}, 51 | 52 | -- Creates and returns a 25px font 53 | font = melon.Font(25) 54 | }) 55 | ``` 56 | 57 | Image/Material System: 58 | ```lua 59 | surface.SetMaterial( 60 | -- Caches material automatically 61 | melon.Material("icon16/user.png", "smooth") 62 | ) 63 | 64 | surface.SetMaterial( 65 | -- Downloads and returns inline 66 | melon.Image("https://i.imgur.com/xYWFeyG.jpg") 67 | ) 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /glualint.json: -------------------------------------------------------------------------------- 1 | { 2 | "lint_maxScopeDepth": 0, 3 | "lint_trailingWhitespace": false, 4 | "lint_gotos": false, 5 | "lint_goto_identifier": false, 6 | "lint_doubleNegations": false, 7 | 8 | "lint_profanity": false 9 | } -------------------------------------------------------------------------------- /lua/autorun/sh_melon_lib_init.lua: -------------------------------------------------------------------------------- 1 | 2 | if melon then 3 | melon.__reloaded = true 4 | melon.FinishedLoading = false 5 | 6 | print() 7 | melon.Log(0, "Reloading") 8 | end 9 | 10 | ---- 11 | ---@module 12 | ---@name melon 13 | ---@realm SHARED 14 | ---- 15 | ---- Main module table for the library 16 | ---- 17 | melon = melon or {} 18 | 19 | ---- 20 | ---@member 21 | ---@name melon.version 22 | ---- 23 | ---- Version in major.minor.patch format, see [melon.ParseVersion] 24 | ---- 25 | melon.version = "1.6.0" 26 | melon.__loadhandlers = melon.__loadhandlers or {} 27 | melon.__loaded = melon.__loaded or {} 28 | melon.__reloaded = melon.__reloaded or false 29 | 30 | ---- 31 | ---@internal 32 | ---@name melon.AddLoadHandler 33 | ---- 34 | ---- Adds a load handler for melonlib, sh_, cl_ and sv_ are all loadhandlers 35 | ---- 36 | function melon.AddLoadHandler(handler, func, module_specific) 37 | melon.__loadhandlers[handler] = { 38 | func, 39 | module_specific 40 | } 41 | end 42 | 43 | ---- 44 | ---@internal 45 | ---@name melon.LoadDirectory 46 | ---- 47 | ---- Loads a directory recursively, for core use 48 | ---- 49 | function melon.LoadDirectory(dir, m, loaded) 50 | local fil, fol = file.Find(dir .. "/*", "LUA") 51 | 52 | for k,v in ipairs(fil) do 53 | if string.GetExtensionFromFilename(v) != "lua" then 54 | continue 55 | end 56 | 57 | local dirs = dir .. "/" .. v 58 | local spl = string.Split(v, "_") 59 | local h = melon.__loadhandlers[spl[1]] 60 | 61 | if h then 62 | if h[2] and h[2] != m then 63 | return 64 | end 65 | h[1](dirs) 66 | else 67 | melon.Log(2, "Invalid File Handler '{1}' found when loading '{2}'", spl[1], dirs) 68 | end 69 | 70 | if melon.__reloaded then 71 | if melon.__loaded[dirs] then 72 | continue 73 | end 74 | 75 | hook.Run("Melon:NewFileDetected", dirs, h) 76 | end 77 | 78 | melon.__loaded[dirs] = true 79 | end 80 | 81 | for k,v in pairs(fol) do 82 | melon.LoadDirectory(dir .. "/" .. v, m) 83 | end 84 | end 85 | 86 | melon.AddLoadHandler("sh", function(f) 87 | AddCSLuaFile(f) 88 | include(f) 89 | end ) 90 | 91 | melon.AddLoadHandler("sv", function(f) 92 | if SERVER then 93 | include(f) 94 | end 95 | end ) 96 | 97 | melon.AddLoadHandler("cl", function(f) 98 | if SERVER then 99 | AddCSLuaFile(f) 100 | else 101 | include(f) 102 | end 103 | end ) 104 | 105 | ---- 106 | ---@internal 107 | ---@name melon.__load 108 | ---- 109 | ---- Loads everything in the library 110 | ---- 111 | function melon.__load() 112 | melon.FinishedLoading = false 113 | 114 | --[[ Preload all needed files ]] 115 | melon.LoadDirectory("melon/preload") 116 | hook.Run("Melon:DoneLoading:PreLoad") 117 | melon.Log(0, "Started Initialization of MelonLib v{1}", melon.version) 118 | 119 | --[[ Load all core files ]] 120 | melon.LoadDirectory("melon/core") 121 | hook.Run("Melon:DoneLoading:Core") 122 | melon.Log(0, "Loaded Core") 123 | 124 | --[[ Load all modules ]] 125 | local _,fol = file.Find("melon/modules/*", "LUA") 126 | 127 | for k,v in pairs(fol) do 128 | melon.LoadModule(v) 129 | end 130 | 131 | hook.Run("Melon:DoneLoading:Modules") 132 | melon.Log(0, "Loaded Modules") 133 | 134 | --[[ All done! ]] 135 | hook.Run("Melon:DoneLoading") 136 | melon.Log(0, "Finished Initialization") 137 | 138 | melon.FinishedLoading = true 139 | end 140 | 141 | melon.__load() 142 | 143 | ---- 144 | ---@concommand melon_raw_reload 145 | ---- 146 | ---- Reloads melonlib 147 | ---- 148 | concommand.Add("melon_raw_reload", function(ply) 149 | melon.__reloaded = true 150 | 151 | if CLIENT or IsValid(ply) then 152 | melon.Log(1, "Missing permissions") 153 | return 154 | end 155 | 156 | melon.Log(2, "Reloading") 157 | melon.__load() 158 | end, nil, nil, FCVAR_CHEAT) -------------------------------------------------------------------------------- /lua/melon/core/data/sh_net.lua: -------------------------------------------------------------------------------- 1 | 2 | if SERVER then 3 | util.AddNetworkString("melon") 4 | end 5 | 6 | ---- 7 | ---@module 8 | ---@name melon.net 9 | ---@realm SHARED 10 | ---- 11 | ---- Network handlers and abstractions 12 | ---- 13 | melon.net = melon.net or {} 14 | 15 | ---- 16 | ---@member 17 | ---@name melon.net.Listeners 18 | ---- 19 | ---- Table of all network listeners 20 | ---- 21 | melon.net.Listeners = melon.net.Listeners or {} 22 | 23 | ---- 24 | ---@name melon.net.Watch 25 | ---- 26 | ---@arg (msg: string) Message name added with [util.AddNetworkString] to watch 27 | ---@arg (name: string) Identifier for the watcher 28 | ---@arg (callback: fn) Function callback for whenever the listener recieves an input 29 | ---- 30 | ---- Watches a network message, replacement for [net.Receive] that takes multiple callbacks 31 | ---- Only use if you desperately need 32 | ---- 33 | ---` 34 | ---` util.AddNetworkString("ping_net_name") 35 | ---` 36 | ---` melon.net.Watch("ping_net_name", "Identifier", function(len, ply) 37 | ---` net.Start("ping_net_name") 38 | ---` net.WriteString("Pong :)") 39 | ---` net.Send(ply) 40 | ---` end ) 41 | ---` 42 | ---` melon.net.Watch("ping_net_name", "Other Identifier", function(len, ply) 43 | ---` print("Ponged from Identifier :)") 44 | ---` end ) 45 | ---` 46 | function melon.net.Watch(msg, name, callback) 47 | if not melon.net.Listeners[msg] then 48 | melon.net.Listeners[msg] = {} 49 | 50 | net.Receive(msg, function(len, ply) 51 | for _, v in pairs(melon.net.Listeners[msg]) do 52 | if v(len, ply) == true then 53 | break 54 | end 55 | end 56 | end ) 57 | end 58 | 59 | melon.net.Listeners[msg][name] = callback 60 | end 61 | 62 | ---- 63 | ---@name melon.net.Unwatch 64 | ---- 65 | ---@arg (msg: string) Message name added with [util.AddNetworkString] 66 | ---@arg (name: string) Identifier of the watcher to be removed 67 | ---- 68 | ---- Unwatches a network message added with [melon.net.Watch] 69 | ---- 70 | ---` melon.net.Unwatch("ping_net_name", "Other Identifier") 71 | function melon.net.Unwatch(msg, name) 72 | melon.net.Listeners[msg][name] = nil 73 | end 74 | 75 | ---- 76 | ---@silence 77 | ---@name melon.net.Recv 78 | ---@alias melon.net.Watch 79 | ---- 80 | melon.net.Recv = melon.net.Watch -------------------------------------------------------------------------------- /lua/melon/core/data/sh_quick_serialize.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@name melon.QuickSerialize 4 | ---- 5 | ---@arg (tbl: table) Table to serialize 6 | ---@return (str: string) Serialized table 7 | ---- 8 | ---- Serializes a table very simply, only allows string keys and values 9 | ---- 10 | ---- format is key::'value;key2::'value2; 11 | ---- 12 | function melon.QuickSerialize(tbl) 13 | local s = "" 14 | 15 | for k,v in pairs(tbl) do 16 | s = s .. tostring(k) .. "::'" .. tostring(v) .. ";" 17 | end 18 | 19 | return s 20 | end 21 | 22 | ---- 23 | ---@name melon.DeQuickSerialize 24 | ---- 25 | ---@arg (str: string) String to deserialize 26 | ---@return (tbl: table) Deserialized table 27 | ---- 28 | ---- Deserialized a table serialized with [melon.QuickSerialize] 29 | ---- 30 | function melon.DeQuickSerialize(s) 31 | local split = string.Split(s, ";") 32 | local toret = {} 33 | 34 | for k,v in pairs(split) do 35 | local spl = string.Split(s, "::'") 36 | toret[spl[1]] = spl[2] 37 | end 38 | 39 | return toret 40 | end 41 | 42 | -------------------------------------------------------------------------------- /lua/melon/core/data/sh_stream.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@class melon.STREAM 4 | ---- 5 | ---- Simple way to pass around a mutable string builder 6 | ---- 7 | local STREAM = {} 8 | STREAM.__index = STREAM 9 | melon.STREAM = STREAM 10 | 11 | function STREAM:Init() 12 | self.data = "" 13 | return self 14 | end 15 | 16 | ---- 17 | ---@method 18 | ---@name melon.STREAM:Write 19 | ---- 20 | ---@arg (data: string) The string to write to the buffer 21 | ---@return (self) 22 | ---- 23 | ---- Appends a string to the end of the buffer, uses the concatenation operator so if youre using this 24 | ---- for tables/objects remember to override this! 25 | ---- 26 | function STREAM:Write(data) 27 | self.data = self.data .. data 28 | return self 29 | end 30 | 31 | ---- 32 | ---@method 33 | ---@name melon.STREAM:WriteFmt 34 | ---- 35 | ---@arg (fmt: string) String format to use, see [melon.string] for a reference 36 | ---@arg (args: ...any) Any values to be passed to the formatter 37 | ---@return (self) 38 | ---- 39 | ---- Appends a [melon.string.Format] formatted string to the end of the buffer 40 | ---- 41 | function STREAM:WriteFmt(fmt, ...) 42 | return self:Write(({melon.string.Format(fmt, ...)})[1]) 43 | end 44 | 45 | ---- 46 | ---@method 47 | ---@name melon.STREAM:WriteLn 48 | ---- 49 | ---@arg (data: string) The string to write to the buffer 50 | ---@return (self) 51 | ---- 52 | ---- Appends a string to the end of the buffer, identical to [melon.STREAM:Write] except it also appends a newline. 53 | ---- 54 | function STREAM:WriteLn(data) 55 | return self:Write(data):Write("\n") 56 | end 57 | 58 | ---- 59 | ---@method 60 | ---@name melon.STREAM:Consume() 61 | ---- 62 | ---@return (str: string) The written string 63 | ---- 64 | ---- Gets the written string and resets the stream, 65 | ---- 66 | function STREAM:Consume() 67 | local d = self.data 68 | self.data = "" 69 | return d 70 | end 71 | 72 | ---- 73 | ---@method 74 | ---@name melon.STREAM:Append 75 | ---- 76 | ---@arg (stream: melon.STREAM) The stream to append, gets consumed 77 | ---@return (self) 78 | ---- 79 | ---- Appends the given stream to the current stream 80 | ---- 81 | function STREAM:Append(stream) 82 | self:Write(stream:Consume()) 83 | 84 | return self 85 | end 86 | 87 | ---- 88 | ---@name melon.NewStream 89 | ---- 90 | ---@return (stream: melon.STREAM) The new stream 91 | ---- 92 | ---- Creates a new [melon.STREAM] object, initialized without any data 93 | ---- 94 | function melon.NewStream() 95 | return setmetatable({}, STREAM):Init() 96 | end -------------------------------------------------------------------------------- /lua/melon/core/data/sh_table.lua: -------------------------------------------------------------------------------- 1 | ---- 2 | ---@name melon.Map 3 | ---- 4 | ---@arg (tbl: table) Table to map 5 | ---@arg (fn: func) Function that takes k,v and returns a new k,v 6 | ---@return (new: table) Table created by the mapper 7 | ---- 8 | ---- Maps a table to a new table, calling func with every key and value. 9 | ---- 10 | function melon.Map(tbl, func) 11 | local new = {} 12 | 13 | for k,v in pairs(tbl) do 14 | local nk, nv = func(k, v) 15 | new[nk] = nv 16 | end 17 | 18 | return new 19 | end 20 | 21 | ---- 22 | ---@name melon.JMap 23 | ---- 24 | ---@arg (iter: func) The iterator function to use. 25 | ---@arg (t: table) The table to map. 26 | ---@arg (fn: func) The mapping function. The function is passed the value then the key, and is expected to return a new value. 27 | ---@return (new: table) A table with the mapped elements. 28 | ---- 29 | ---- Implements the JavaScript style map function. 30 | ---- 31 | ---- Maps a table to a new table, calling `fn` with every key/value pair. 32 | ---- 33 | function melon.JMap(iter, t, fn) 34 | local new = {} 35 | for key, value in iter(t) do 36 | new[key] = fn(value, key) 37 | end 38 | return new 39 | end 40 | 41 | ---- 42 | ---@name melon.Reduce 43 | ---- 44 | ---@arg (iter: func) The iterator function to use. 45 | ---@arg (t: table) The table to reduce. 46 | ---@arg (fn: func) The reducer function. The function is passed the current value, the value, the key, and returns the next value. 47 | ---@arg (initial_value: any) The initial value passed to `fn`. 48 | ---@return (new: any) The reduced value. 49 | ---- 50 | ---- Performs a reduction on the given `iter` with the given `fn`. 51 | ---- 52 | function melon.Reduce(iter, t, fn, initial_value) 53 | local current_value = initial_value 54 | for key, value in iter(t) do 55 | current_value = fn(current_value, value, key) 56 | end 57 | return current_value 58 | end 59 | 60 | ---- 61 | ---@name melon.Find 62 | ---- 63 | ---@arg (iter: func) The iterator function to use. 64 | ---@arg (t: table) The table to search in. 65 | ---@arg (fn: func) The reducer function. The function is passed the value, the key, and returns a boolean. 66 | ---@return (new: any) The found value, or nil. 67 | ---- 68 | ---- Loops through `t` using `iter` and tests each element with `fn`. The first to return true 69 | ---- 70 | function melon.Find(iter, t, fn) 71 | local current_value = initial_value 72 | for key, value in iter(t) do 73 | current_value = fn(current_value, value, key) 74 | end 75 | return current_value 76 | end 77 | 78 | ---- 79 | ---@name melon.GroupBy 80 | ---- 81 | ---@arg (iter: func) The iterator function to use. 82 | ---@arg (t: table) The table to group. 83 | ---@arg (fn: func) The reducer function. The function is passed the value, the key, and returns a boolean. 84 | ---@return (new: any) A table of tables, containing the groups. 85 | ---- 86 | ---- Loops through `t` using `iter`. `fn` is expected to return a value for each iteration which will be used to group 87 | ---- the elements of the `iter`. The returned value is a table whose keys are those returned values from `fn`. 88 | ---- 89 | function melon.GroupBy(iter, t, fn) 90 | local o = {} 91 | for key, value in iter(t) do 92 | local g = fn(value, key) 93 | if not o[g] then 94 | o[g] = {} 95 | end 96 | 97 | table.insert(o[g], value) 98 | end 99 | return o 100 | end 101 | 102 | 103 | ---- 104 | ---@name melon.Join 105 | ---- 106 | ---@arg (iter: func) The iterator function to use. 107 | ---@arg (t: table) The table to join. 108 | ---@arg (sep: string) The reducer function. The function is passed the value, the key, and returns a boolean. 109 | ---@return (new: string) The joined value. 110 | --- 111 | ---- 112 | ---- Loops through `t` using `iter` and concats each element into a string separated by `sep`. 113 | ---- 114 | function melon.Join(iter, t, sep) 115 | local o = "" 116 | for _, value in iter(t) do 117 | o = o .. value .. sep 118 | end 119 | o = o:sub(1, #o - #sep) 120 | return o 121 | end 122 | 123 | ---- 124 | ---@name melon.KV2VK 125 | ---- 126 | ---@arg (tbl: table) Table to convert 127 | ---@return (new: table) Converted table 128 | ---- 129 | ---- Inverts a tables keys and values ([k] = v) into ([v] = k) for every pair in the given table. 130 | ---- 131 | function melon.KV2VK(tbl) 132 | return melon.Map(tbl, function(k, v) 133 | return v, k 134 | end) 135 | end 136 | 137 | ---- 138 | ---@name melon.SubTable 139 | ---- 140 | ---@arg (tbl: table) Table to get the subtable of 141 | ---@arg (from: number) Starting index 142 | ---@arg (to: number) Ending index 143 | ---@return (sub: table) Subtable of the given arguments 144 | ---- 145 | ---- Gets a subtable of the given table from the range of from to to, think string.sub() 146 | ---- 147 | function melon.SubTable(tbl, from, to) 148 | local new = {} 149 | 150 | for i = from, to do 151 | table.insert(new, tbl[i]) 152 | end 153 | 154 | return new 155 | end 156 | 157 | ---- 158 | ---@name melon.Pack 159 | ---- 160 | ---@arg (args: varargs) The varargs to pack. 161 | ---@return (length: number) The length of the varargs. 162 | ---@return (args: table) A table containing the passed varargs. 163 | ---- 164 | ---- Takes a variable length of arguments and packs them into a table while also returning the length. 165 | ---- 166 | function melon.Pack(...) 167 | return select("#", ...), { ... } 168 | end 169 | 170 | ---- 171 | ---@name melon.XPack 172 | ---- 173 | ---@arg (success: bool) The success value to forward. 174 | ---@arg (args: varargs) The varargs to pack. 175 | ---@return (length: number) The length of the varargs. 176 | ---@return (args: table) A table containing the passed varargs. 177 | ---- 178 | ---- Takes a variable length of arguments and packs them into a table while also returning the length. 179 | ---- 180 | ---- This function also forwards an initial `success` value separately of the rest of the varargs. 181 | ---- 182 | function melon.XPack(success, ...) 183 | return success, select("#", ...), { ... } 184 | end -------------------------------------------------------------------------------- /lua/melon/core/data/sh_url_sanitize.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@internal 4 | ---@name melon.URLExtension 5 | ---- 6 | ---@arg (url: string) URL to get the file extension of 7 | ---@return (ext: string) Extension of the URL given 8 | ---- 9 | ---- Gets the extension of a url, relic of the ancient past, dont use. 10 | ---- 11 | function melon.URLExtension(url) 12 | local spl = string.Split(url, ".") 13 | 14 | return spl[#spl] 15 | end 16 | 17 | ---- 18 | ---@name melon.SanitizeURL 19 | ---- 20 | ---@arg (url: string) URL to sanitize 21 | ---@return (new: string) Sanitized URL 22 | ---- 23 | ---- Sanitize a URL for use in filenames, literally only allows alpha characters 24 | ---- 25 | function melon.SanitizeURL(url) 26 | return ({url:gsub("%W", "")})[1] 27 | end -------------------------------------------------------------------------------- /lua/melon/core/data/sql/sv_adapters.lua: -------------------------------------------------------------------------------- 1 | 2 | melon.sql = melon.sql or {} 3 | 4 | ---- 5 | ---@member 6 | ---@name melon.sql.Adapters 7 | ---- 8 | ---- A table of all created sql adapters 9 | ---- 10 | melon.sql.Adapters = melon.sql.Adapters or {} 11 | 12 | do --- sqlite 13 | melon.sql.Adapters.sqlite = {} 14 | 15 | function melon.sql.Adapters.sqlite:Connect() end 16 | 17 | function melon.sql.Adapters.sqlite:Query(done, query) 18 | local q = sql.Query(query) 19 | 20 | if q == false then 21 | return melon.sql.Adapters.sqlite.Error() 22 | end 23 | 24 | done(melon.sql.Adapters.sqlite.SortOutput(self, query, q)) 25 | end 26 | 27 | function melon.sql.Adapters.sqlite:Error() 28 | melon.Log(1, "[sqlite] Error occurred ({})", sql.LastError()) 29 | end 30 | 31 | function melon.sql.Adapters.sqlite:Escape(value) 32 | return isstring(value) and sql.SQLStr(value, true) or value 33 | end 34 | 35 | function melon.sql.Adapters.sqlite:SortOutput(query, output) 36 | return output 37 | end 38 | end 39 | 40 | do --- mysqloo 41 | melon.sql.Adapters.mysqloo = {} 42 | 43 | function melon.sql.Adapters.mysqloo:Connect() 44 | if not mysqloo and not util.IsBinaryModuleInstalled("mysqloo") then 45 | melon.Log(1, "[mysqloo] Failed to use mysqloo module, are you sure you installed the correct version? ") 46 | return false 47 | elseif not mysqloo then 48 | require("mysqloo") 49 | end 50 | 51 | if not self:GetHost() then melon.Log(1, "[mysqloo] Expected Host, Use Connection:SetHost(...)") return false end 52 | if not self:GetUsername() then melon.Log(1, "[mysqloo] Expected Username, Use Connection:SetUsername(...)") return false end 53 | if not self:GetPassword() then melon.Log(1, "[mysqloo] Expected Password, Use Connection:SetPassword(...)") return false end 54 | if not self:GetDatabase() then melon.Log(1, "[mysqloo] Expected Database, Use Connection:SetDatabase(...)") return false end 55 | 56 | self.InternalMysqlooConnection = mysqloo.connect( 57 | self:GetHost(), 58 | self:GetUsername(), 59 | self:GetPassword(), 60 | self:GetDatabase(), 61 | self:GetPort() 62 | ) 63 | 64 | function self.InternalMysqlooConnection.onConnectionFailed(_, err) 65 | melon.sql.Adapters.mysqloo.Error(self, "Connection Failed! " .. err) 66 | end 67 | 68 | self.InternalMysqlooConnection:setAutoReconnect(true) 69 | self.InternalMysqlooConnection:setMultiStatements(false) 70 | self.InternalMysqlooConnection:connect() 71 | 72 | return true 73 | end 74 | 75 | function melon.sql.Adapters.mysqloo:Error(message) 76 | return melon.Log(1, "[mysqloo] An error occurred, {}", message) 77 | end 78 | 79 | function melon.sql.Adapters.mysqloo:Query(done, query) 80 | if not (self.InternalMysqlooConnection:status() == mysqloo.DATABASE_CONNECTED) then 81 | return self:Error("Mysqloo not connected") 82 | end 83 | 84 | local q = self.InternalMysqlooConnection:query(query) 85 | 86 | function q.onError(_, err, cause) 87 | self:Error("Query failed (" .. err .. ") from `" .. cause .. "`") 88 | end 89 | 90 | function q.onSuccess(_, data) 91 | done(melon.sql.Adapters.mysqloo.SortOutput(self, query, data)) 92 | end 93 | 94 | q:start() 95 | end 96 | 97 | function melon.sql.Adapters.mysqloo:Escape(value) 98 | return self.InternalMysqlooConnection:escape(tostring(value)) 99 | end 100 | 101 | function melon.sql.Adapters.mysqloo:SortOutput(query, output) 102 | return output 103 | end 104 | end -------------------------------------------------------------------------------- /lua/melon/core/data/sql/sv_connection.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@realm SERVER 4 | ---@name melon.sql 5 | ---- 6 | ---- Contains everything needed to interact with multiple SQL drivers 7 | ---- 8 | melon.sql = melon.sql or {} 9 | 10 | ---- 11 | ---@name melon.sql.NewConnection 12 | ---- 13 | ---@return (conn: melon.sql.Connection) The connection object 14 | ---- 15 | ---- Creates a new connection object 16 | ---- 17 | function melon.sql.NewConnection() 18 | return setmetatable({}, melon.sql.Connection):Init() 19 | end 20 | 21 | ---- 22 | ---@class melon.sql.Connection 23 | ---- 24 | ---@accessor (SupportedAdapters: table) A table of adapters each query is required to support 25 | ---@accessor (Adapter: string) The connections adapter, what kind of sql variant its using 26 | ---@accessor (Host: string) The remote host of this connection, if applicable 27 | ---@accessor (Username: string) The remote username of this connection, if applicable 28 | ---@accessor (Password: string) The password of this connection, if applicable 29 | ---@accessor (Database: string) The database of this connection, if applicable 30 | ---@accessor (Port: string) The port of this connection, if applicable 31 | ---- 32 | ---- The central SQL connection that handles I/O 33 | ---- 34 | melon.sql.Connection = {} 35 | melon.sql.Connection.__index = melon.sql.Connection 36 | 37 | melon.AccessorFunc(melon.sql.Connection, "SupportedAdapters", {"sqlite", "mysqloo"}) 38 | melon.AccessorFunc(melon.sql.Connection, "Adapter", "sqlite") 39 | melon.AccessorFunc(melon.sql.Connection, "Host") 40 | melon.AccessorFunc(melon.sql.Connection, "Username") 41 | melon.AccessorFunc(melon.sql.Connection, "Password") 42 | melon.AccessorFunc(melon.sql.Connection, "Database") 43 | melon.AccessorFunc(melon.sql.Connection, "Port") 44 | 45 | function melon.sql.Connection:Init() 46 | return self 47 | end 48 | 49 | ---- 50 | ---@method 51 | ---@name melon.sql.Connection:Connect 52 | ---- 53 | ---- Attempts to connect to the sql server, if applicable 54 | ---- 55 | function melon.sql.Connection:Connect() 56 | local adapter = melon.sql.Adapters[self:GetAdapter()] 57 | 58 | if not adapter then 59 | melon.Log(1, "[sql] Invalid adapter {}", self:GetAdapter()) 60 | 61 | return false 62 | end 63 | 64 | if adapter.Connect(self) != false then 65 | return melon.Log(3, "[sql] Successfully connected on adapter {}", self:GetAdapter()) 66 | end 67 | end 68 | 69 | ---- 70 | ---@method 71 | ---@name melon.sql.Connection:Query 72 | ---- 73 | ---@arg (done: func) The function to call with the output values when finished 74 | ---@arg (adapters: table) A table of adapter keys with query values 75 | ---@arg (...values: any) Varargs of values to be escaped in those queries 76 | ---@return (passed: bool) Did the query get validated successfully? 77 | ---- 78 | ---- Sends a query to the connection and calls done with whatever the query returned 79 | ---- 80 | function melon.sql.Connection:Query(done, adapters, ...) 81 | local failure = false 82 | 83 | for k, v in pairs(self:GetSupportedAdapters()) do 84 | if not adapters[v] then 85 | melon.Log(1, "[sql] Missing required query for adapter '{}', please add it on line {}", v, debug.getinfo(2, "l").currentline) 86 | failure = true 87 | end 88 | end 89 | 90 | if failure then return false end 91 | 92 | local adapter = melon.sql.Adapters[self:GetAdapter()] 93 | if not adapter then 94 | melon.Log(1, "[sql] Invalid adapter {}", self:GetAdapter()) 95 | 96 | return false 97 | end 98 | 99 | adapter.Query( 100 | self, 101 | done, 102 | melon.sql.QueryParse( 103 | adapters[self:GetAdapter()], 104 | function(val) 105 | return adapter.Escape(self, val) 106 | end, 107 | {...} 108 | ) 109 | ) 110 | end 111 | 112 | ---- 113 | ---@method 114 | ---@name melon.sql.Connection:QueryNoResult 115 | ---- 116 | ---@arg (adapters: table) A table of adapter keys with query values 117 | ---@arg (...values: any) Varargs of values to be escaped in those queries 118 | ---@return (passed: bool) Did the query get validated successfully? 119 | ---- 120 | ---- Sends a query to the connection 121 | ---- 122 | function melon.sql.Connection:QueryNoResult(adapters, ...) 123 | return self:Query(function() end, adapters, ...) 124 | end 125 | 126 | melon.Debug(function() 127 | local conn = melon.sql.NewConnection() 128 | 129 | conn:SetAdapter("mysqloo") 130 | 131 | conn:SetHost("localhost") 132 | conn:SetUsername("root") 133 | conn:SetPassword("") 134 | conn:SetDatabase("goobles") 135 | 136 | conn:Connect() 137 | 138 | conn:Query(function(data) 139 | _p(data) 140 | end, { 141 | sqlite = "select * from ?2? where steamid='?1?'", 142 | mysqloo = "select * from ?2? where steamid='?1?'" 143 | }, "76561198009689185", "achievements") 144 | end, true) -------------------------------------------------------------------------------- /lua/melon/core/data/sql/sv_parsers.lua: -------------------------------------------------------------------------------- 1 | 2 | melon.sql = melon.sql or {} 3 | 4 | ---- 5 | ---@name melon.sql.QueryParse 6 | ---- 7 | ---@arg (query: string) The query to parse and convert 8 | ---@arg (escape: func) The escape function 9 | ---@arg (values: table) The values to be injected into the query 10 | ---@return (query: string) The converted query 11 | ---- 12 | ---- Takes an input query with the placeholder `??` or `??`, eg. `SELECT * FROM ?4?`, and converts it into an escaped query 13 | ---- 14 | function melon.sql.QueryParse(query, escape, values) 15 | for k, v in pairs(values) do 16 | values[k] = escape(v) 17 | end 18 | 19 | local current = 1 20 | 21 | return ({string.gsub(query, "%?%d?%?", function(rep) 22 | if #rep == 2 then 23 | current = current + 1 24 | 25 | return values[current - 1] 26 | end 27 | 28 | rep = string.sub(rep, 2, -2) 29 | 30 | return values[tonumber(rep)] 31 | end )})[1] 32 | end -------------------------------------------------------------------------------- /lua/melon/core/drawing/cl_blur.lua: -------------------------------------------------------------------------------- 1 | 2 | local blurMat = Material("pp/blurscreen") 3 | 4 | ---- 5 | ---@name melon.DrawBlur 6 | ---- 7 | ---@arg (panel: panel) Panel to draw the blur on 8 | ---@arg (localX: number) X relative to 0 of the panel 9 | ---@arg (localY: number) Y relative to 0 of the panel 10 | ---@arg (w: number) W of the blur 11 | ---@arg (h: number) H of the blur 12 | ---@arg (passes: number) How many passes to run, basically the strength of the blur 13 | ---- 14 | ---- Draws blur! 15 | ---- 16 | function melon.DrawBlur(panel, localX, localY, w, h, passes) 17 | if passes == 0 then return end 18 | local x, y = panel:LocalToScreen(localX, localY) 19 | 20 | surface.SetMaterial(blurMat) 21 | surface.SetDrawColor(255, 255, 255) 22 | 23 | for i = 0, (passes or 6) do 24 | blurMat:SetFloat("$blur", i * .33) 25 | blurMat:Recompute() 26 | end 27 | render.UpdateScreenEffectTexture() 28 | surface.DrawTexturedRect(x * -1, y * -1, ScrW(), ScrH()) 29 | end 30 | 31 | ---- 32 | ---@name melon.DrawPanelBlur 33 | ---- 34 | ---@arg (panel: panel) Panel to draw the blur on 35 | ---@arg (passes: type) How many passes to run, basically the strength of the blur 36 | ---- 37 | ---- Draws blur based on a given panels dimensions 38 | ---- 39 | function melon.DrawPanelBlur(panel, passes) 40 | local x,y = panel:LocalToScreen(0, 0) 41 | 42 | surface.SetMaterial(blurMat) 43 | surface.SetDrawColor(255, 255, 255) 44 | 45 | for i = 0, (passes or 6) do 46 | blurMat:SetFloat("$blur", i * .33) 47 | blurMat:Recompute() 48 | end 49 | render.UpdateScreenEffectTexture() 50 | surface.DrawTexturedRect(x * -1, y * -1, ScrW(), ScrH()) 51 | end -------------------------------------------------------------------------------- /lua/melon/core/drawing/cl_images.lua: -------------------------------------------------------------------------------- 1 | 2 | file.CreateDir("melon") 3 | file.CreateDir("melon/images") 4 | 5 | ---- 6 | ---@silence 7 | ---@member 8 | ---@name melon.InvalidImage 9 | ---@deprecated 10 | ---- 11 | ---- Material for loading images, currently the israeli flag 12 | ---- 13 | melon.InvalidImage = melon.InvalidImage or Material("flags16/il.png") 14 | local images = {} 15 | 16 | ---- 17 | ---@name melon.Image 18 | ---- 19 | ---@arg (url: string) URL to the image to download 20 | ---@arg (callback: func) Function to be called when download finished/image retrieved, called with IMaterial 21 | ---- 22 | ---- Remote image downloader and cache handler. Discord is unreliable, use imgur. 23 | ---- 24 | function melon.Image(url, callback) 25 | if images[url] then 26 | if callback then callback(true, images[url]) end 27 | return images[url] 28 | end 29 | 30 | local sans = melon.SanitizeURL(url) 31 | local ext = melon.URLExtension(url) 32 | local rext = "." .. ext 33 | 34 | if ext == "vtf" then 35 | rext = "" 36 | end 37 | 38 | if file.Exists("melon/images/" .. sans .. "." .. ext , "DATA") then 39 | melon.Log(3, "Image load success from filesystem '{1}.{2}'", sans, ext) 40 | 41 | images[url] = Material("../data/melon/images/" .. sans .. rext, "mips smooth") 42 | if callback then callback(true, images[url]) end 43 | return images[url] 44 | end 45 | 46 | melon.Log(3, "Image Fetch made to {1}", "https://external-content.duckduckgo.com/iu/?u=" .. url) 47 | melon.http.Get("https://external-content.duckduckgo.com/iu/?u=" .. url, function(bod, size, headers, code) 48 | file.Write("melon/images/" .. sans .. "." .. ext, bod) 49 | images[url] = Material("../data/melon/images/" .. sans .. rext, "mips smooth") 50 | melon.Log(3, "Image Download Success: '{1}' ({2})", url, code) 51 | 52 | if callback then callback(true, images[url]) end 53 | end, function(err) 54 | melon.Log(1, "Image Download Failure: '{1}'", err) 55 | 56 | if callback then callback(false, err) end 57 | end ) 58 | 59 | images[url] = melon.InvalidImage 60 | 61 | return images[url] 62 | end 63 | 64 | hook.Add("InitPostEntity", "Melon:LoadLoadingImage", function() 65 | melon.Image("https://i.imgur.com/635PPvg.png", function(success, mat) 66 | if success then 67 | melon.InvalidImage = mat 68 | else 69 | melon.InvalidImage = true 70 | end 71 | end ) 72 | end ) 73 | 74 | ---- 75 | ---@name melon.DrawImage 76 | ---- 77 | ---@arg (url: string) URL to the image you want to draw 78 | ---@arg (x: number) X of the image to draw 79 | ---@arg (y: number) Y of the image to draw 80 | ---@arg (w: number) W of the image to draw 81 | ---@arg (h: number) H of the image to draw 82 | ---@return (drawn: bool) If the image has been drawn or not, false if loading, true if drawn 83 | ---- 84 | ---- Draw an image, handles loading and everything else for you, for use in a 2d rendering hook. 85 | ---- 86 | function melon.DrawImage(url, x, y, w, h) 87 | local mat = melon.Image(url) 88 | 89 | if mat == melon.InvalidImage then 90 | local size = math.min(w, h) 91 | surface.SetMaterial(mat) 92 | surface.SetDrawColor(255, 255, 255, 200 + math.sin(CurTime() * 2) * 30) 93 | surface.DrawTexturedRectRotated(x + w / 2, y + h / 2, size, size, CurTime() * 2) 94 | return false 95 | end 96 | 97 | surface.SetMaterial(mat) 98 | surface.DrawTexturedRect(x, y, w, h) 99 | return true 100 | end 101 | 102 | ---- 103 | ---@name melon.DrawImageRotated 104 | ---- 105 | ---@arg (url: string) URL to the image you want to draw 106 | ---@arg (x: number) X of the image to draw 107 | ---@arg (y: number) Y of the image to draw 108 | ---@arg (w: number) W of the image to draw 109 | ---@arg (h: number) H of the image to draw 110 | ---@arg (rot: number) Rotation of the image to draw 111 | ---@return (drawn: bool) If the image has been drawn or not, false if loading, true if drawn 112 | ---- 113 | ---- Identical to [melon.DrawImage] except draws it rotated 114 | ---- 115 | function melon.DrawImageRotated(url, x, y, w, h, rot) 116 | local mat = melon.Image(url) 117 | 118 | if mat == melon.InvalidImage then 119 | local size = math.min(w, h) 120 | surface.SetMaterial(mat) 121 | surface.SetDrawColor(255, 255, 255, 200 + math.sin(CurTime() * 2) * 30) 122 | surface.DrawTexturedRectRotated(x, y, size, size, CurTime() * 2) 123 | return false 124 | end 125 | 126 | surface.SetMaterial(mat) 127 | surface.DrawTexturedRectRotated(x, y, w, h, rot or 0) 128 | return true 129 | end 130 | 131 | 132 | local avatars = {} 133 | ---- 134 | ---@internal 135 | ---@name melon.GetPlayerAvatar 136 | ---- 137 | ---@arg (stid64: string) SteamID64 of the players avatar youd like to get 138 | ---@return (avatar: IMaterial) Material of the players avatar, unreliable, will return nil if invalid 139 | ---- 140 | ---- Gets a players avatar image from the cache if it exists and initiates downloading it if not, dont use. 141 | ---- 142 | function melon.GetPlayerAvatar(steamid) 143 | if avatars[steamid] then 144 | return avatars[steamid] 145 | end 146 | 147 | avatars[steamid] = melon.InvalidImage 148 | melon.thirdparty.getAvatarMaterial(steamid, function(mat) 149 | avatars[steamid] = mat 150 | end ) 151 | 152 | return avatars[steamid] 153 | end 154 | 155 | ---- 156 | ---@name melon.DrawAvatar 157 | ---- 158 | ---@arg (stid: string) SteamID64 of the player to draw the avatar of 159 | ---@arg (x: number) X of the image to draw 160 | ---@arg (y: number) Y of the image to draw 161 | ---@arg (w: number) W of the image to draw 162 | ---@arg (h: number) H of the image to draw 163 | ---@return (drawn: bool) If the image has been drawn or not, false if loading, true if drawn 164 | ---- 165 | ---- Draws a players avatar image reliably. 166 | ---- 167 | function melon.DrawAvatar(steamid, x, y, w, h) 168 | local mat = melon.GetPlayerAvatar(steamid) 169 | 170 | if mat == melon.InvalidImage then 171 | local size = math.min(w, h) 172 | surface.SetMaterial(mat) 173 | surface.SetDrawColor(255, 255, 255, 200 + math.sin(CurTime() * 2) * 30) 174 | surface.DrawTexturedRectRotated(x + w / 2, y + h / 2, size, size, CurTime() * 2) 175 | return false 176 | end 177 | 178 | surface.SetMaterial(mat) 179 | surface.DrawTexturedRect(x, y, w, h) 180 | return true 181 | end -------------------------------------------------------------------------------- /lua/melon/core/drawing/cl_matcache.lua: -------------------------------------------------------------------------------- 1 | 2 | local mats = {} 3 | 4 | ---- 5 | ---@name melon.Material 6 | ---- 7 | ---@arg (path: string) Path to the image 8 | ---@arg (opts: string) Optional, Options to give the material, identical to [Material]'s second arg 9 | ---@return (name: IMaterial) Material to the path given 10 | ---- 11 | ---- Automatically caches and returns materials, helper function for rendering hooks, identical to [Material] except cached 12 | ---- 13 | function melon.Material(path, opts) 14 | opts = opts or "none" 15 | if mats[path .. opts] then 16 | return mats[path .. opts] 17 | end 18 | 19 | mats[path .. opts] = Material(path, opts) 20 | return mats[path .. opts] 21 | end -------------------------------------------------------------------------------- /lua/melon/core/drawing/cl_noise.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@realm CLIENT 4 | ---@name melon.noise 5 | ---- 6 | ---- Handles noise generation and materials 7 | ---- 8 | melon.noise = melon.noise or {} 9 | 10 | melon.noise.size = 2048 * 2 11 | melon.noise.pixels = 0 12 | melon.noise.rt = GetRenderTargetEx("MelonLib_Noise1", melon.noise.size, melon.noise.size, RT_SIZE_OFFSCREEN, MATERIAL_RT_DEPTH_NONE, bit.bor(1, 256), 0, IMAGE_FORMAT_RGBA8888) 13 | melon.noise.mat = CreateMaterial("MelonLib_Noise1", "UnlitGeneric", { 14 | ["$basetexture"] = melon.noise.rt:GetName(), 15 | ["$translucent"] = "1", 16 | ["$vertexalpha"] = "1", 17 | ["$vertexcolor"] = "1", 18 | }) 19 | 20 | render.PushRenderTarget(melon.noise.rt) 21 | render.Clear(255, 255, 255, 0) 22 | render.PopRenderTarget() 23 | 24 | ---- 25 | ---@name melon.noise.GetMaterial 26 | ---- 27 | ---@return (noise: IMaterial) The Noise Material 28 | ---- 29 | ---- Gets the noise material generated 30 | ---- 31 | function melon.noise.GetMaterial() 32 | return melon.noise.mat 33 | end 34 | 35 | ---- 36 | ---@name melon.noise.Draw 37 | ---- 38 | ---@arg (x: number) X Coord 39 | ---@arg (y: number) Y Coord 40 | ---@arg (w: number) Width 41 | ---@arg (h: number) Height 42 | ---@arg (xoff: number) X Offset 43 | ---@arg (yoff: number) Y Offset 44 | ---- 45 | ---- Draws the noise material with the given offsets 46 | ---- 47 | function melon.noise.Draw(x, y, w, h, xoff, yoff) 48 | xoff = xoff or 0 49 | yoff = yoff or 0 50 | 51 | surface.SetMaterial(melon.noise.mat) 52 | surface.DrawTexturedRectUV(x, y, w, h, xoff, yoff, xoff + melon.noise.size / w + (x / w), yoff + melon.noise.size / h + (y / h)) 53 | end 54 | 55 | ---- 56 | ---@internal 57 | ---@name melon.noise.DrawRandomPixel 58 | ---- Adds a pixel to the material 59 | function melon.noise.DrawRandomPixel() 60 | local x, y, alpha = math.Rand(0, melon.noise.size), math.Rand(0, melon.noise.size), math.Rand(0, 255) 61 | 62 | surface.SetDrawColor(255, 255, 255, alpha) 63 | surface.DrawRect(x, y, 1, 1) 64 | end 65 | 66 | -- melon.DebugPanel("DPanel", function(p) 67 | -- p:SetSize(ScrW(), ScrH() / 2) 68 | -- p:Center() 69 | -- function p:Paint(w, h) 70 | -- surface.SetDrawColor(22, 22, 22) 71 | -- surface.DrawRect(0, 0, w, h) 72 | 73 | -- surface.SetDrawColor(255, 255, 255, 20) 74 | -- surface.SetMaterial(melon.noise.mat) 75 | 76 | -- melon.noise.Draw(0, 0, w, h) 77 | -- end 78 | -- end ) 79 | 80 | hook.Add("Think", "Melon:NoiseGenerate", function() 81 | local oc = DisableClipping(true) 82 | 83 | render.PushRenderTarget(melon.noise.rt) 84 | cam.Start2D() 85 | 86 | for i = 0, (1 / FrameTime()) * 6 do 87 | melon.noise.pixels = melon.noise.pixels + 1 88 | melon.noise.DrawRandomPixel() 89 | end 90 | 91 | cam.End2D() 92 | render.PopRenderTarget() 93 | 94 | DisableClipping(oc) 95 | 96 | if melon.noise.pixels >= 1500000 then 97 | melon.Log(3, "Noise Material Finished Generating!") 98 | melon.noise.pixels = false 99 | hook.Remove("Think", "Melon:NoiseGenerate") 100 | end 101 | end ) -------------------------------------------------------------------------------- /lua/melon/core/drawing/cl_panel_dev_suite.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@internal 4 | ---@module 5 | ---@name melon.PanelDevSuite 6 | ---@realm CLIENT 7 | ---- 8 | ---- Everything internally to do with the panel development suite 9 | ---- 10 | melon.PanelDevSuite = melon.PanelDevSuite or {} 11 | melon.PanelDevSuite.Theme = {} 12 | melon.PanelDevSuite.Theme.Background = Color(22, 22, 22) 13 | melon.PanelDevSuite.Theme.Midground = Color(33, 33, 33) 14 | melon.PanelDevSuite.Theme.Foreground = Color(44, 44, 44) 15 | melon.PanelDevSuite.Theme.Shadow = Color(10, 10, 10) 16 | melon.PanelDevSuite.Theme.Accent = Color(61,64,226) 17 | melon.PanelDevSuite.Theme.BadAccent = Color(226,64,61) 18 | melon.PanelDevSuite.Theme.Text = Color(255, 255, 255) 19 | melon.PanelDevSuite.Theme.SecondaryText = Color(255, 255, 255, 80) 20 | melon.PanelDevSuite.Theme.AccentText = Color(131, 133, 255) 21 | melon.PanelDevSuite.Theme.Transparent = Color(0, 0, 0, -255) 22 | 23 | melon.PanelDevSuite.Tabs = { 24 | -- {"3D View", "Melon:PanelSuite:Tab:3DView"}, 25 | {"Elements", "Melon:PanelSuite:Tab:Elements"}, 26 | {"Render Inspect", "Melon:PanelSuite:Tab:RenderView"}, 27 | -- {"Something Else1", "DPanel"}, 28 | -- {"Another Tab1", "DPanel"}, 29 | -- {"Something Else2", "DPanel"}, 30 | -- {"Another Tab2", "DPanel"}, 31 | -- {"Something Else3", "DPanel"}, 32 | -- {"Another Tab3", "DPanel"}, 33 | -- {"Something Else4", "DPanel"}, 34 | -- {"Another Tab4", "DPanel"}, 35 | } 36 | ---- 37 | ---@name melon.DebugPanel2 38 | ---- 39 | ---@arg (name: string) Panel name registered with [vgui.Register] 40 | ---@arg (fun: func) Function thats called with the panel as its only argument 41 | ---- 42 | ---- Creates a debug panel containing the given function, lay this out in fun(), visual and functional improvement of [melon.DebugPanel] 43 | ---- 44 | function melon.DebugPanel2(name, fn, nofocus) 45 | if not melon.Debug() then return end 46 | if melon.DebugPanel2_PanelInstance then 47 | melon.DebugPanel2_PanelInstance:Remove() 48 | end 49 | 50 | melon.DebugPanel2_PanelInstance = vgui.Create("Melon:PanelSuite:Main") 51 | melon.DebugPanel2_PanelInstance:SetSize(ScrW(), ScrH()) 52 | melon.DebugPanel2_PanelInstance:SetPos(0, 0) 53 | melon.DebugPanel2_PanelInstance:SetSuitePanelType(name) 54 | melon.DebugPanel2_PanelInstance:SetSuiteFunction(fn) 55 | melon.DebugPanel2_PanelInstance:SuiteReady() 56 | 57 | if nofocus then 58 | melon.DebugPanel2_PanelInstance:SetMouseInputEnabled(false) 59 | melon.DebugPanel2_PanelInstance:SetKeyboardInputEnabled(false) 60 | end 61 | 62 | hook.Add("HUDShouldDraw", "melon.DebugPanel2", function(n) 63 | if not IsValid(melon.DebugPanel2_PanelInstance) then 64 | hook.Remove("HUDShouldDraw", "melon.DebugPanel2") 65 | return 66 | end 67 | 68 | if not melon.DebugPanel2_PanelInstance.allowed_to_draw then return end 69 | if melon.DebugPanel2_PanelInstance.allowed_to_draw[n] then 70 | return 71 | end 72 | 73 | return false 74 | end ) 75 | 76 | hook.Add("PreDrawViewModel", "melon.DebugPanel2", function(vm, pl, wep) 77 | if not IsValid(melon.DebugPanel2_PanelInstance) then 78 | hook.Remove("PreDrawViewModel", "melon.DebugPanel2") 79 | return 80 | end 81 | 82 | if not melon.DebugPanel2_PanelInstance.allowed_to_draw then return end 83 | if melon.DebugPanel2_PanelInstance.allowed_to_draw.viewmodel then 84 | return 85 | end 86 | 87 | return true 88 | end ) 89 | end 90 | 91 | ---- 92 | ---@internal 93 | ---@name melon.DebugPanel2_HookPaint 94 | ---- 95 | function melon.DebugPanel2_HookPaint(pnl, pre, post) 96 | pnl.DebugPanel2_HookPaint = pnl.DebugPanel2_HookPaint or pnl.Paint 97 | 98 | pnl.Paint = function(s, w, h) 99 | if not IsValid(melon.DebugPanel2_PanelInstance) then 100 | return melon.DebugPanel2_UnHookPaint(pnl) 101 | end 102 | 103 | if pre then 104 | pre(s, w, h) 105 | end 106 | 107 | pnl.DebugPanel2_HookPaint(s, w, h) 108 | 109 | if post then 110 | post(s, w, h) 111 | end 112 | end 113 | end 114 | 115 | ---- 116 | ---@internal 117 | ---@name melon.DebugPanel2_UnHookPaint 118 | ---- 119 | function melon.DebugPanel2_UnHookPaint(pnl) 120 | if pnl.DebugPanel2_HookPaint then 121 | pnl.Paint = pnl.DebugPanel2_HookPaint 122 | pnl.DebugPanel2_HookPaint = nil 123 | end 124 | end 125 | 126 | melon.DebugPanel2__TEST = function() 127 | if not melon.Debug() then return end 128 | 129 | local PANEL = vgui.Register("DebugPanel2TestPanel", {}, "DPanel") 130 | 131 | local r = function(s,w,h) 132 | surface.SetDrawColor(ColorRand()) 133 | surface.DrawRect(0,0,w,h) 134 | end 135 | function PANEL:Init() 136 | for _ = 0, 5 do 137 | local p = vgui.Create("DPanel", self) 138 | p:Dock(TOP) 139 | p:SetTall(100) 140 | p:DockPadding(math.random(0, 20), 0, math.random(0, 20), 0) 141 | p.Paint = r 142 | 143 | for _ = 0, 10 do 144 | local q = vgui.Create("DPanel", p) 145 | q:Dock(TOP) 146 | q:SetTall(10) 147 | q.Paint = r 148 | end 149 | end 150 | end 151 | 152 | melon.DebugPanel2("DebugPanel2TestPanel", function(p) 153 | p:InvalidateLayout(true) 154 | -- p:SetSize(500, p:SizeToChildren(false, true)) 155 | p:SizeToChildren(false, true) 156 | p:Center() 157 | -- p:CenterHorizontal() 158 | end ) 159 | end 160 | 161 | melon.DebugPanel2__TEST() 162 | 163 | melon.DebugPanel = melon.DebugPanel2 -------------------------------------------------------------------------------- /lua/melon/core/drawing/cl_scaling.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@name melon.ScaleBy 4 | ---- 5 | ---@arg (by: number) Number to scale by 6 | ---@return (func: func(number) -> number) Function that scales by the given factor 7 | ---- 8 | ---- Creates a function to scale a number by 9 | ---- 10 | function melon.ScaleBy(by) 11 | return function(v) 12 | return v * by 13 | end 14 | end 15 | 16 | ---- 17 | ---@silence 18 | ---@name melon.Scale 19 | ---- 20 | ---@arg (numin: number) Number to scale 21 | ---@return (numout: number) Scaled number 22 | ---- 23 | ---- Scales a number based on [ScrH] / 1080 24 | ---- 25 | melon.Scale = melon.ScaleBy(ScrH() / 1080) 26 | hook.Add("OnScreenSizeChanged", "Melon:ResetScale", function() 27 | melon.Scale = melon.ScaleBy(ScrH() / 1080) 28 | end ) 29 | 30 | ---- 31 | ---@name melon.ScaleN 32 | ---- 33 | ---@arg (numin: ...number) Vararg numbers to scale 34 | ---@return (numout: ...number) Unpacked scaled numbers 35 | ---- 36 | ---- Scales multiple numbers 37 | ---- 38 | function melon.ScaleN(a,b,c,d,e,f) 39 | return 40 | a and melon.Scale(a), 41 | b and melon.Scale(b), 42 | c and melon.Scale(c), 43 | d and melon.Scale(d), 44 | e and melon.Scale(e), 45 | f and melon.Scale(f) 46 | end 47 | 48 | melon.Debug(function() 49 | cprint(melon.ScaleN(1, 2, 3)) 50 | end ) -------------------------------------------------------------------------------- /lua/melon/core/drawing/cl_stencil.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@module 4 | ---@name melon.stencil 5 | ---@realm CLIENT 6 | ---- 7 | ---- Stencil helpers 8 | ---- 9 | ---` melon.stencil.Start() 10 | ---` surface.SetDrawColor(255, 255, 255) 11 | ---` surface.DrawTexturedRectRotated(w / 2, h / 2, w, 50, CurTime() * 10) 12 | ---` melon.stencil.Cut() 13 | ---` draw.Text({ 14 | ---` text = "Some Text", 15 | ---` pos = {w / 2, h / 2}, 16 | ---` yalign = 1, 17 | ---` xalign = 1, 18 | ---` font = melon.Font(60), 19 | ---` color = Color(255, 0, 0) 20 | ---` }) 21 | ---` melon.stencil.End() 22 | melon.stencil = melon.stencil or {} 23 | melon.stencil.open = false 24 | 25 | ---- 26 | ---@name melon.stencil.Start 27 | ---- 28 | ---- Starts the cutout 29 | ---- 30 | function melon.stencil.Start() 31 | if melon.stencil.open then 32 | Error("Starting a stencil without closing it") 33 | debug.Trace() 34 | 35 | melon.stencil.End() 36 | return 37 | end 38 | 39 | melon.stencil.open = true 40 | render.ClearStencil() 41 | render.SetStencilEnable(true) 42 | 43 | render.SetStencilWriteMask(1) 44 | render.SetStencilTestMask(1) 45 | 46 | render.SetStencilFailOperation(STENCILOPERATION_REPLACE) 47 | render.SetStencilPassOperation(STENCILOPERATION_ZERO) 48 | render.SetStencilZFailOperation(STENCILOPERATION_ZERO) 49 | render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_NEVER) 50 | render.SetStencilReferenceValue(1) 51 | end 52 | 53 | ---- 54 | ---@name melon.stencil.Cut 55 | ---- 56 | ---- Tells the stencil to start cutting 57 | ---- 58 | function melon.stencil.Cut() 59 | render.SetStencilFailOperation(STENCILOPERATION_ZERO) 60 | render.SetStencilPassOperation(STENCILOPERATION_REPLACE) 61 | render.SetStencilZFailOperation(STENCILOPERATION_ZERO) 62 | render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) 63 | render.SetStencilReferenceValue(1) 64 | end 65 | 66 | ---- 67 | ---@name melon.stencil.Deny 68 | ---- 69 | ---- Tells the stencil to start cutting but flip it, deny everything thats drawn! 70 | ---- 71 | function melon.stencil.Deny() 72 | render.SetStencilFailOperation(STENCILOPERATION_REPLACE) 73 | render.SetStencilPassOperation(STENCILOPERATION_ZERO) 74 | render.SetStencilZFailOperation(STENCILOPERATION_REPLACE) 75 | render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) 76 | render.SetStencilReferenceValue(0) 77 | end 78 | 79 | ---- 80 | ---@name melon.stencil.End 81 | ---- 82 | ---- Ends the cutout 83 | ---- 84 | function melon.stencil.End() 85 | melon.stencil.open = false 86 | render.SetStencilEnable(false) 87 | render.ClearStencil() 88 | end 89 | 90 | melon.DebugPanel("DPanel", function(p) 91 | p:SetSize(500, 500) 92 | p:Center() 93 | 94 | local TestText = "Testing Text Hehe" 95 | function p:Paint(w, h) 96 | surface.SetFont(melon.Font(60)) 97 | draw.Text({ 98 | text = TestText, 99 | pos = {w / 2, h / 2}, 100 | yalign = 1, 101 | xalign = 1, 102 | font = melon.Font(60), 103 | color = Color(0, 255, 0) 104 | }) 105 | 106 | melon.stencil.Start() 107 | surface.SetDrawColor(255, 255, 255) 108 | surface.DrawTexturedRectRotated(w / 2, h / 2, w, 50, CurTime() * 10) 109 | melon.stencil.Deny() 110 | draw.Text({ 111 | text = TestText, 112 | pos = {w / 2, h / 2}, 113 | yalign = 1, 114 | xalign = 1, 115 | font = melon.Font(60), 116 | color = Color(255, 0, 0) 117 | }) 118 | melon.stencil.End() 119 | end 120 | end ) -------------------------------------------------------------------------------- /lua/melon/core/drawing/cl_superellipse.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@name melon.GenerateSuperellipse 4 | ---- 5 | ---@arg (n: number) The number that defines the shape of the superellipse 6 | ---@arg (x: number) X coordinate of the superellipse 7 | ---@arg (y: number) Y coordinate of the superellipse 8 | ---@arg (w: number) Width of the superellipse 9 | ---@arg (h: number) Height of the superellipse 10 | ---@arg (resolution: number) Resolution of the superellipse, defaults to 4, lower the number, higher the quality 11 | ---@return (poly: table) Polygon table, for use with [surface.DrawPoly] 12 | ---- 13 | ---- Generates a superellipse or "squircle" polygon, see https://en.wikipedia.org/wiki/Superellipse 14 | ---- 15 | function melon.GenerateSuperellipse(n, x, y, w, h, resolution) 16 | n = math.max(0, n) 17 | 18 | local poly = {} 19 | 20 | for i = 0, 360, (resolution or 4) do 21 | local r = math.rad(i) 22 | local s = math.sin(r) 23 | local c = math.cos(r) 24 | 25 | local v = (1 / ((math.abs(c) / (w / 2)) ^ n + (math.abs(s) / (h / 2)) ^ n)) ^ (1 / n); 26 | 27 | local xx = (x + w / 2) + (v * c) 28 | local yy = (y + h / 2) + (v * s) 29 | 30 | table.insert(poly, { 31 | x = xx, 32 | y = yy, 33 | u = xx / w, 34 | v = yy / h 35 | }) 36 | end 37 | 38 | return poly 39 | end 40 | 41 | ---- 42 | ---@name melon.GenerateSuperellipseMesh 43 | ---- 44 | ---@arg (n: number) The number that defines the shape of the superellipse 45 | ---@arg (x: number) X coordinate of the superellipse 46 | ---@arg (y: number) Y coordinate of the superellipse 47 | ---@arg (w: number) Width of the superellipse 48 | ---@arg (h: number) Height of the superellipse 49 | ---@arg (resolution: number) Resolution of the superellipse, defaults to 5, lower the number, higher the quality 50 | ---@return (mesh: IMesh) Mesh of the given superellipse 51 | ---- 52 | ---- Identical to [melon.GenerateSuperellipse] except this generates a Mesh instead of a Polygon 53 | ---- 54 | function melon.GenerateSuperellipseMesh(n, x, y, w, h, resolution) 55 | local poly = melon.GenerateSuperellipse(n, x, y, w, h, resolution) 56 | 57 | local cv = Vector(x + w / 2, y + h / 2) 58 | local b = {} 59 | 60 | for k, v in pairs(poly) do 61 | local next = poly[k + 1] or poly[1] 62 | 63 | table.insert(b, { 64 | color = color_white, 65 | pos = Vector(v.x, v.y), 66 | u = v.u, 67 | v = v.v 68 | }) 69 | 70 | table.insert(b, { 71 | color = color_white, 72 | pos = Vector(next.x, next.y), 73 | u = next.u, 74 | v = next.v 75 | }) 76 | 77 | table.insert(b, { 78 | color = color_white, 79 | pos = cv, 80 | u = 0.5, 81 | v = 0.5 82 | }) 83 | end 84 | 85 | local m = Mesh() 86 | m:BuildFromTriangles(b) 87 | return m 88 | end -------------------------------------------------------------------------------- /lua/melon/core/drawing/cl_text.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@module 4 | ---@name melon.text 5 | ---@realm CLIENT 6 | ---- 7 | ---- Misc text handling functions 8 | ---- 9 | 10 | melon.text = melon.text or {} 11 | melon.text.delimiters = melon.text.delimiters or {} 12 | 13 | ---- 14 | ---@name melon.text.AddDelimiter 15 | ---- 16 | ---@arg (delimiter: char) Character to count as a delimiter 17 | ---- 18 | ---- Adds a delimiter for string splitting, wrapping, selection, etc 19 | ---- 20 | function melon.text.AddDelimiter(delim) 21 | melon.text.delimiters[delim] = true 22 | end 23 | 24 | local base_delims = "/\\()\"'-.,:;<>~!@#$%^&*|+=[]{}~?│ \t" 25 | for i = 1, #base_delims do 26 | melon.text.AddDelimiter(base_delims[i]) 27 | end 28 | 29 | ---- 30 | ---@name melon.text.Wrap 31 | ---- 32 | ---@arg (text: string) String to process 33 | ---@arg (font: string) Font of the text 34 | ---@arg (w: number) Maximum width of the wrapped text 35 | ---@arg (x: number) Optional, Starting X pos of the text 36 | ---@arg (notrim: bool) Optional, Dont trim the left of each line 37 | ---@return (lines: table) Table of all lines wrapped 38 | ---- 39 | ---- Wraps the given text based on newlines and text width, breaks word if it cant resolve a better way. 40 | ---- VERY SLOW, cache this result. 41 | ---- 42 | function melon.text.Wrap(text, font, w, x, notrim) 43 | x = x or 0 44 | 45 | local line = "" 46 | local last_delim = false 47 | local lines = {} 48 | 49 | surface.SetFont(font) 50 | local index = 0 51 | while index <= #text do 52 | index = index + 1 53 | local char = text[index] 54 | local tw = surface.GetTextSize(line .. char) 55 | 56 | if tw >= (w - x) then 57 | if last_delim then 58 | table.insert(lines, last_delim[1]) 59 | index = last_delim[2] 60 | line = "" 61 | last_delim = false 62 | else 63 | table.insert(lines, line) 64 | line = char 65 | end 66 | 67 | x = 0 68 | continue 69 | elseif char == "\n" then 70 | table.insert(lines, line) 71 | line = char 72 | last_delim = false 73 | continue 74 | end 75 | 76 | line = line .. char 77 | 78 | if not notrim then 79 | line = string.TrimLeft(line) 80 | end 81 | 82 | if melon.text.delimiters[char] then 83 | last_delim = { 84 | line, 85 | index 86 | } 87 | end 88 | end 89 | 90 | if line != "" then 91 | table.insert(lines, line) 92 | end 93 | 94 | return lines 95 | end 96 | 97 | ---- 98 | ---@name melon.text.Ellipses 99 | ---- 100 | ---@arg (text: string) Text to cut 101 | ---@arg (font: string) Font to scale the text with 102 | ---@arg (w: number) Maximum width of the text 103 | ---@return (text: string) Cut text 104 | ---- 105 | ---- Crops text and ends it with ... if it fills the given width or more. 106 | ---- VERY SLOW, cache this result. 107 | ---- 108 | function melon.text.Ellipses(text, font, w) 109 | surface.SetFont(font) 110 | local tw = surface.GetTextSize(text) 111 | if tw <= w then 112 | return text 113 | end 114 | 115 | for i = 1, #text do 116 | if select(1, surface.GetTextSize(text:sub(1, i))) >= w then 117 | return text:sub(1, i - 3) .. "..." 118 | end 119 | end 120 | 121 | return "..." -- kek 122 | end 123 | 124 | melon.DebugPanel("Panel", function(pnl) 125 | pnl:SetSize(500, 500) 126 | pnl:Center() 127 | 128 | function pnl:Paint(w, h) 129 | surface.SetDrawColor(22,22,22) 130 | surface.DrawRect(0, 0, w, h) 131 | 132 | surface.SetDrawColor(melon.colors.Rainbow()) 133 | surface.DrawOutlinedRect(1,1,w-2,h-2) 134 | 135 | local font = melon.Font(22) 136 | local y = 10 137 | for k,v in pairs(melon.text.Wrap(string.rep("TextWrapTestTextWrapTestTextWr\nTextWrapTestTextWrapTestapTestTextWrapTestTextWrapTestTextWrapTestTextWrapTest", 2, " "), font, w - 20, nil)) do 138 | local _, th = draw.Text({ 139 | text = v, 140 | pos = {10, y}, 141 | font = font, 142 | xalign = 0 143 | }) 144 | 145 | y = y + th 146 | end 147 | 148 | draw.Text({ 149 | text = melon.text.Ellipses(string.rep("EllipsesTextTest", 10, ""), font, w - 20), 150 | pos = {w / 2, h - 10}, 151 | xalign = 1, 152 | yalign = 4, 153 | font = font 154 | }) 155 | end 156 | end ) 157 | -------------------------------------------------------------------------------- /lua/melon/core/drawing/cl_vgui.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@module 4 | ---@name melon.panels 5 | ---@realm CLIENT 6 | ---- 7 | ---- Misc panel helpers 8 | ---- 9 | 10 | melon.panels = melon.panels or {} 11 | 12 | function melon.panels.DebugPaint(pnl, w, h) 13 | surface.SetDrawColor(22, 22, 22) 14 | surface.DrawOutlinedRect(0, 0, w, h, 3) 15 | 16 | surface.SetDrawColor(22, 22, 22, 100) 17 | surface.DrawRect(0,0,w,h) 18 | 19 | surface.SetDrawColor(melon.colors.Rainbow()) 20 | surface.DrawOutlinedRect(1, 1, w - 2, h - 2) 21 | 22 | draw.Text({ 23 | text = pnl.ClassName or "anonPanel", 24 | pos = {w / 2 + 2, h / 2 + 2}, 25 | xalign = 1, 26 | yalign = 1, 27 | font = melon.Font(40), 28 | color = Color(22, 22, 22) 29 | }) 30 | 31 | draw.Text({ 32 | text = pnl.ClassName or "anonPanel", 33 | pos = {w / 2, h / 2}, 34 | xalign = 1, 35 | yalign = 1, 36 | font = melon.Font(40), 37 | }) 38 | end 39 | 40 | -- melon.DebugPanel("DPanel", function(pnl) 41 | -- pnl:SetSize(500,500) 42 | -- pnl:Center() 43 | -- pnl.Paint = melon.panels.DebugPaint 44 | -- end ) 45 | 46 | local lines 47 | ---- 48 | ---@name melon.DebugLine 49 | ---- 50 | ---@arg (x: number) X Coord to put the line at 51 | ---@arg (y: number) Y Coord to put the line at 52 | ---@arg (p: Panel?) Panel this line is relative to 53 | ---@arg (id: string?) String to render next to this line 54 | ---- 55 | ---- Creates a line rendered on screen until the next melon.Debug call 56 | ---- 57 | function melon.DebugLine(x, y, panel, id) 58 | id = id or string.char(string.byte('a') + (#(lines or {}))) 59 | 60 | if not lines then 61 | local size = melon.Scale(24) 62 | local dot = melon.Scale(2) 63 | 64 | local function d(xx, yy) 65 | surface.DrawRect(xx - size - dot + 1, yy, size, 1) 66 | surface.DrawRect(xx + dot, yy, size, 1) 67 | 68 | surface.DrawRect(xx, yy - dot - size + 1, 1, size) 69 | surface.DrawRect(xx, yy + dot, 1, size) 70 | 71 | surface.DrawRect(xx, yy, 1, 1) 72 | end 73 | 74 | hook.Add("PostRenderVGUI", "Melon:Debug:DrawLines", function() 75 | for k,v in pairs(lines) do 76 | surface.SetDrawColor(22, 22, 22) 77 | d(v[1] + 1, v[2] + 1) 78 | 79 | surface.SetDrawColor(v[3]) 80 | d(v[1], v[2]) 81 | 82 | if id then 83 | draw.TextShadow({ 84 | text = v[4], 85 | pos = {v[1] + size + (dot * 4), v[2]}, 86 | yalign = 1, 87 | }, 1) 88 | end 89 | end 90 | end ) 91 | 92 | lines = {} 93 | end 94 | 95 | if IsValid(panel) then 96 | local lx, ly = panel:LocalToScreen(0, 0) 97 | x = x + lx 98 | y = y + ly 99 | end 100 | 101 | table.insert(lines, {x, y, color_white, id}) -- melon.colors.Rainbow(1) 102 | end 103 | 104 | hook.Add("Melon:Debug", "ClearLines", function() 105 | lines = nil 106 | hook.Remove("PostRenderVGUI", "Melon:Debug:DrawLines") 107 | end ) 108 | 109 | melon.Debug() 110 | melon.DebugPanel("DPanel", function(p) 111 | function p:Paint(w, h) 112 | surface.SetDrawColor(255, 0, 0) 113 | surface.DrawRect(0,0,w,h) 114 | end 115 | 116 | function p:OnMousePressed() 117 | local x,y = self:LocalCursorPos() 118 | melon.DebugLine(x, y, self) 119 | end 120 | end ) -------------------------------------------------------------------------------- /lua/melon/core/drawing/sh_colors.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@module 4 | ---@name melon.colors 5 | ---@realm CLIENT 6 | ---- 7 | ---- Handles color modification and other things 8 | ---- 9 | melon.colors = melon.colors or {} 10 | 11 | ---- 12 | ---@name melon.colors.Copy 13 | ---- 14 | ---@arg (original: Color) To color 15 | ---@return (new: Color) New color object 16 | ---- 17 | ---- Returns a new [Color] thats a copy of the original given 18 | ---- 19 | function melon.colors.Copy(original) 20 | return Color( 21 | original.r, 22 | original.g, 23 | original.b, 24 | original.a 25 | ) 26 | end 27 | 28 | ---- 29 | ---@name melon.colors.CopyShallow 30 | ---- 31 | ---@arg (original: Color) To color 32 | ---@return (new: table) New color 33 | ---- 34 | ---- Returns a new table thats a copy of the original given, 35 | ---- same as [melon.colors.Copy] but without the metatable 36 | ---- 37 | function melon.colors.CopyShallow(original) 38 | return { 39 | r = original.r, 40 | g = original.g, 41 | b = original.b, 42 | a = original.a 43 | } 44 | end 45 | 46 | ---- 47 | ---@name melon.colors.Lerp 48 | ---- 49 | ---@arg (amt: number) Amount to interpolate by 50 | ---@arg (from: Color) From color 51 | ---@arg (to: Color) To color 52 | ---@return (new: Color) New color object 53 | ---- 54 | ---- Returns a new [Color] thats interpolated by from/to 55 | ---- 56 | function melon.colors.Lerp(amt, from, to) 57 | return { 58 | r = Lerp(amt, from.r, to.r), 59 | g = Lerp(amt, from.g, to.g), 60 | b = Lerp(amt, from.b, to.b), 61 | a = Lerp(amt, from.a, to.a) 62 | } 63 | end 64 | 65 | ---- 66 | ---@name melon.colors.LerpMod 67 | ---- 68 | ---@arg (amt: number) Amount to interpolate by 69 | ---@arg (mod: Color) Color to modify 70 | ---@arg (to: Color) To color 71 | ---- 72 | ---- Modifies the given color for optimization reasons, be careful. 73 | ---- 74 | function melon.colors.LerpMod(amt, mod, to) 75 | mod.r = Lerp(amt, mod.r, to.r) 76 | mod.g = Lerp(amt, mod.g, to.g) 77 | mod.b = Lerp(amt, mod.b, to.b) 78 | mod.a = Lerp(amt, mod.a, to.a) 79 | end 80 | 81 | -- Credit: Billy (bvgui_v2.lua:242) 82 | ---- 83 | ---@name melon.colors.IsLight 84 | ---- 85 | ---@arg (col: Color) Color to check 86 | ---@return (dark: bool) Is the color light or dark 87 | ---- 88 | ---- Get if a color is dark or light, primarily for dynamic text colors 89 | ---- 90 | function melon.colors.IsLight(col) 91 | return (col.r * 0.299 + col.g * 0.587 + col.b * 0.114) > 186 92 | end 93 | 94 | ---- 95 | ---@name melon.colors.Rainbow 96 | ---- 97 | ---@arg (mul: number) Number to multiply time by, optional 98 | ---@arg (offset: number) Hue to offset the time by 99 | ---@return (color: Color) Rainbow color 100 | ---- 101 | ---- Generates a consistent rainbow color 102 | ---- 103 | function melon.colors.Rainbow(mul, offset) 104 | return HSVToColor(CurTime() * (mul or 20) + (offset or 0), 0.9, 0.9) 105 | end 106 | 107 | ---- 108 | ---@metadata AcceptsHexColor 109 | ---- 110 | ---@arg (hex: string) Hex color 111 | ---@return (col: Color) New color object 112 | ---- 113 | ---- Converts a hex color of 3, 4, 6 or 8 characters into a [Color] object 114 | ---- 115 | function melon.colors.FromHex(hex) 116 | local str = hex:gsub("#", "") 117 | if #str == 3 then 118 | str = (hex[1] .. hex[1]) .. (hex[2] .. hex[2]) .. (hex[3] .. hex[3]) 119 | elseif #str == 4 then 120 | str = (hex[1] .. hex[1]) .. (hex[2] .. hex[2]) .. (hex[3] .. hex[3]) .. (hex[4] .. hex[4]) 121 | end 122 | 123 | if #str == 6 then 124 | str = str .. "FF" -- alpha 125 | end 126 | 127 | local r = tonumber("0x" .. str:sub(1, 2)) 128 | local g = tonumber("0x" .. str:sub(3, 4)) 129 | local b = tonumber("0x" .. str:sub(5, 6)) 130 | local a = tonumber("0x" .. str:sub(7, 8)) 131 | 132 | if r and g and b and a then 133 | return Color(r, g, b, a) 134 | end 135 | end 136 | 137 | ---- 138 | ---@name melon.colors.ToHex 139 | ---- 140 | ---@arg (color: Color) Real Color 141 | ---@return (hex: string) Hex Color Output 142 | ---- 143 | ---- Converts a Color to a hex color of 3, 4, 6 or 8 chars 144 | ---- 145 | function melon.colors.ToHex(color) 146 | local function isd(t) 147 | return t[1] == t[2] 148 | end 149 | 150 | local r = bit.tohex(color.r, 2) 151 | local g = bit.tohex(color.g, 2) 152 | local b = bit.tohex(color.b, 2) 153 | local a = bit.tohex(color.a, 2) 154 | 155 | if isd(r) and isd(g) and isd(b) and isd(a) then 156 | r,g,b,a = r[1], g[1], b[1], a[1] 157 | end 158 | 159 | if color.a == 255 then a = "" end 160 | 161 | return r .. g .. b .. a 162 | end 163 | 164 | ---- 165 | ---@name melon.colors.Alpha 166 | ---- 167 | ---@arg (color: Color) The color 168 | ---@arg (alpha: number) The wanted alpha 169 | ---@return (color: Color) The new, modified color 170 | ---- 171 | ---- Creates a quick copy of the given color with the alpha given 172 | ---- 173 | function melon.colors.Alpha(color, alpha) 174 | return { 175 | r = color.r, 176 | g = color.g, 177 | b = color.b, 178 | a = alpha 179 | } 180 | end 181 | 182 | ---- 183 | ---@name melon.colors.FastColor 184 | ---- 185 | ---@arg (r: number) The red component 186 | ---@arg (g: number) The red component 187 | ---@arg (b: number) The red component 188 | ---@arg (a: number) The red component 189 | ---@return (color: table) The new fake color 190 | ---- 191 | ---- Creates a color without the color metatable 192 | ---- 193 | function melon.colors.FastColor(r, g, b, a) 194 | g = g or r 195 | b = b or g 196 | a = a or 255 197 | 198 | return { 199 | r = r, 200 | g = g, 201 | b = b, 202 | a = a 203 | } 204 | end 205 | 206 | ---- 207 | ---@alias melon.colors.FastColor 208 | ---@name melon.colors.FC 209 | ---- 210 | melon.colors.FC = melon.colors.FastColor -------------------------------------------------------------------------------- /lua/melon/core/drawing/sh_fonts.lua: -------------------------------------------------------------------------------- 1 | 2 | if SERVER then 3 | resource.AddWorkshop("2631771632") 4 | return 5 | end 6 | 7 | ---- 8 | ---@silence 9 | ---@name melon.Font 10 | ---- 11 | ---@arg (size: number) Font size to be scaled 12 | ---@arg (font: string) Optional, font to base the new font off of 13 | ---@arg (weight: number) Optional, font weight 14 | ---@return (name: string) Font identifier 15 | ---- 16 | ---- For use in 2d rendering hooks, create a font if it doesnt exist with the given size/fontname. 17 | ---- 18 | local fonts = {} 19 | function melon.Font(size, font, weight) 20 | font = font or "Poppins" 21 | fonts[font] = fonts[font] or {} 22 | 23 | local id = size .. (weight or "normal") 24 | if fonts[font][id] then 25 | return fonts[font][id] 26 | end 27 | 28 | font = font or "Poppins" 29 | local name = "melon_lib:" .. font .. ":" .. id 30 | surface.CreateFont(name, { 31 | font = font, 32 | size = melon.Scale(size), 33 | weight = weight 34 | }) 35 | 36 | fonts[font][id] = name 37 | 38 | return name 39 | end 40 | 41 | ---- 42 | ---@silence 43 | ---@name melon.SpecialFont 44 | ---- 45 | ---@arg (size: number) Font size 46 | ---@arg (opts: table) Options to give the font 47 | ---@return (name: string) Font identifier 48 | ---- 49 | ---- Same as [melon.Font] except creates it with a [FontData] table instead of a font name. 50 | ---- Dont use in rendering hooks as it is exponentially slower 51 | ---- 52 | local specfonts = {} 53 | function melon.SpecialFont(size, options) 54 | local ser = melon.QuickSerialize(options) .. "_" .. tostring(size) 55 | 56 | if specfonts[ser] then 57 | return specfonts[ser] 58 | end 59 | 60 | options.size = melon.Scale(size) 61 | 62 | surface.CreateFont("melon_lib:spec:" .. ser, options) 63 | specfonts[ser] = "melon_lib:spec:" .. ser 64 | 65 | return specfonts[ser] 66 | end 67 | 68 | 69 | ---- 70 | ---@silence 71 | ---@name melon.UnscaledFont 72 | ---- 73 | ---@arg (size: number) Font size raw 74 | ---@arg (font: string) Optional, font to base the new font off of 75 | ---@return (name: string) Font identifier 76 | ---- 77 | ---- Same as [melon.Font] except the size is unscale. 78 | ---- 79 | local unscaled = {} 80 | function melon.UnscaledFont(size, font, weight) 81 | font = font or "Poppins" 82 | unscaled[font] = unscaled[font] or {} 83 | 84 | local id = size .. (weight or "normal") 85 | if unscaled[font][id] then 86 | return unscaled[font][id] 87 | end 88 | 89 | font = font or "Poppins" 90 | local name = "melon_lib:unscaled:" .. font .. ":" .. id 91 | surface.CreateFont(name, { 92 | font = font, 93 | size = size, 94 | weight = weight 95 | }) 96 | 97 | unscaled[font][id] = name 98 | 99 | return name 100 | end 101 | 102 | ---- 103 | ---@class 104 | ---@name melon.FontGeneratorObject 105 | ---- 106 | ---- Font Generator Object 107 | ---- 108 | local gen = {} 109 | 110 | ---- 111 | ---@name melon.FontGenerator 112 | ---- 113 | ---@arg (font: string) Font name for the generator to use 114 | ---@return (gen: melon.FontGeneratorObject) [melon.FontGeneratorObject] that has the given font 115 | ---- 116 | ---- Creates a [melon.FontGeneratorObject], an object that allows you to use the font system to 117 | ---- consistently create fonts of the same font without constant config indexing. 118 | ---- 119 | function melon.FontGenerator(fontname) 120 | return setmetatable({ 121 | font = fontname 122 | }, { 123 | __index = gen, 124 | __call = function(s, ...) return s:Font(...) end 125 | }) 126 | end 127 | 128 | ---- 129 | ---@method 130 | ---@name melon.FontGeneratorObject.Font 131 | ---- 132 | ---@arg (size: number) Font size to be scaled 133 | ---@arg (weight: number) Font weight 134 | ---@return (font: string) Font identifier 135 | ---- 136 | ---- Creates a new font with the given size and font from the object 137 | ---- 138 | function gen:Font(size, weight) 139 | return melon.Font(self:Preprocess(size, self.font, weight)) 140 | end 141 | 142 | ---- 143 | ---@method 144 | ---@name melon.FontGeneratorObject.Unscaled 145 | ---- 146 | ---@arg (size: number) Font size 147 | ---@arg (weight: number) Font weight 148 | ---@return (font: string) Font identifier 149 | ---- 150 | ---- Identical to [melon.FontGeneratorObject.Font] except unscaled 151 | ---- 152 | function gen:Unscaled(size, weight) 153 | return melon.UnscaledFont(self:Preprocess(size, self.font, weight)) 154 | end 155 | 156 | ---- 157 | ---@method 158 | ---@name melon.FontGeneratorObject.Preprocess 159 | ---- 160 | ---@arg (size: number) Size of the wanted font 161 | ---@arg (font: string) Font of the wanted font 162 | ---@arg (weight: number) Weight of the wanted font 163 | ---@return (size: number) New size of the wanted font 164 | ---@return (weight: number) New font of the wanted font 165 | ---@return (font: string) New weight of the wanted font 166 | ---- 167 | ---- Called every time a font is wanted 168 | ---- This is to be used to adjust font sizes globally, or whatever else you need 169 | ---- 170 | function gen:Preprocess(size, font, weight) 171 | return size, font, weight 172 | end 173 | 174 | ---- 175 | ---@internal 176 | ---@concommand melon_reload_fonts 177 | ---@realm CLIENT 178 | ---- 179 | ---- Resets all fonts forcefully 180 | ---- 181 | concommand.Add("melon_reload_fonts", function() 182 | fonts = {} 183 | melon.Log(3, "Refreshed Fonts") 184 | end ) 185 | 186 | hook.Add("OnScreenSizeChanged", "Melon:FontReset", function() 187 | fonts = {} 188 | melon.Log(3, "Refreshed Fonts") 189 | end) -------------------------------------------------------------------------------- /lua/melon/core/elements/cl_button.lua: -------------------------------------------------------------------------------- 1 | 2 | melon.elements = melon.elements or {} 3 | 4 | ---- 5 | ---@panel Melon:Button 6 | ---@name melon.elements.Button 7 | ---- 8 | ---- Generic unstyled button 9 | ---- 10 | local PANEL = vgui.Register("Melon:Button", {}, "Panel") 11 | PANEL.DoubleClickTime = 0.18 12 | 13 | melon.elements.Button = PANEL 14 | 15 | function PANEL:Init() 16 | self.pressing = {} 17 | self.pressing_count = 0 18 | self.clicked = {} 19 | self:SetCursor("hand") 20 | end 21 | 22 | function PANEL:OnMousePressed(m) 23 | if self.pressing[m] then 24 | self.pressing[m] = nil 25 | self.pressing_count = self.pressing_count - 1 26 | self:Click(m, true) 27 | 28 | return 29 | end 30 | 31 | self.pressing_count = self.pressing_count + 1 32 | self.pressing[m] = CurTime() + self.DoubleClickTime 33 | end 34 | 35 | function PANEL:ButtonThink() 36 | local ct = CurTime() 37 | for k,v in pairs(self.pressing) do 38 | if ct < v then continue end 39 | 40 | if not input.IsMouseDown(k) then 41 | self.pressing[k] = nil 42 | self:Click(k, false) 43 | self.pressing_count = self.pressing_count - 1 44 | end 45 | end 46 | end 47 | 48 | function PANEL:Think() 49 | self:ButtonThink() 50 | end 51 | 52 | function PANEL:Paint(w, h) 53 | melon.panels.DebugPaint(self, w, h) 54 | end 55 | 56 | ---- 57 | ---@method 58 | ---@name melon.elements.Button:CanClick 59 | ---- 60 | ---@arg (enum: MOUSE_) What was the MOUSE_ enum of the click 61 | ---@arg (double: bool) Was this click a double click? 62 | ---@return (can_click: bool) Should we fire the click event? 63 | ---- 64 | ---- Override this to determine whether or not to fire click events 65 | ---- 66 | function PANEL:CanClick(m, double) 67 | return true 68 | end 69 | 70 | ---- 71 | ---@method 72 | ---@name melon.elements.Button:Click 73 | ---- 74 | ---@arg (enum: MOUSE_) What was the MOUSE_ enum of the click 75 | ---@arg (double: bool) Was this click a double click? 76 | ---- 77 | ---- Called on any click, dont override this unless you have to 78 | ---- 79 | function PANEL:Click(m, double) 80 | if not self:CanClick(m, double) then return end 81 | 82 | if m == MOUSE_LEFT then 83 | self:LeftClick(double) 84 | return 85 | elseif m == MOUSE_RIGHT then 86 | self:RightClick(double) 87 | return 88 | end 89 | 90 | self:OtherClick(m, double) 91 | end 92 | 93 | ---- 94 | ---@method 95 | ---@name melon.elements.Button:LeftClick 96 | ---- 97 | ---@arg (double: bool) Was this click a double click? 98 | ---- 99 | ---- Called on left click, override this. 100 | ---- 101 | function PANEL:LeftClick(double) 102 | end 103 | 104 | ---- 105 | ---@method 106 | ---@name melon.elements.Button:RightClick 107 | ---- 108 | ---@arg (double: bool) Was this click a double click? 109 | ---- 110 | ---- Called on right click, override this. 111 | ---- 112 | function PANEL:RightClick(double) 113 | end 114 | 115 | ---- 116 | ---@method 117 | ---@name melon.elements.Button:OtherClick 118 | ---- 119 | ---@arg (enum: MOUSE_) What was the MOUSE_ enum of the click 120 | ---@arg (double: bool) Was this click a double click? 121 | ---- 122 | ---- Called on any click that isnt right or left. 123 | ---- 124 | function PANEL:OtherClick(enum, double) 125 | end 126 | 127 | melon.DebugPanel("Melon:Button", function(pnl) 128 | pnl:SetSize(500, 500) 129 | pnl:Center() 130 | end) 131 | -------------------------------------------------------------------------------- /lua/melon/core/elements/cl_draggable.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@module 4 | ---@name melon.elements 5 | ---@realm CLIENT 6 | ---- 7 | ---- Contains all PANEL objects added by the library 8 | ---- 9 | melon.elements = melon.elements or {} 10 | 11 | ---- 12 | ---@panel Melon:Draggable 13 | ---@name melon.elements.Draggable 14 | ---- 15 | ---@accessor (AreaOf: panel) Panel to drag when dragging this panel, think a topbar of a frame 16 | ---@accessor (Bounded: panel) Panel to limit the draggable area to, you can also pass true for the parent or false to disable 17 | ---- 18 | ---- Draggable Panel object 19 | ---- 20 | ---` 21 | ---` local p = vgui.Create("DPanel") 22 | ---` p:SetSize(400, 400) 23 | ---` p:Center() 24 | ---` p:MakePopup() 25 | ---` 26 | ---` p.drag = vgui.Create("Melon:Draggable", p) 27 | ---` p.drag:SetSize(200, 200) 28 | ---` p.drag:Center() 29 | ---` p.drag:SetAreaOf(p) 30 | ---` 31 | local PANEL = vgui.Register("Melon:Draggable", {}, "Panel") 32 | AccessorFunc(PANEL, "area", "AreaOf") -- takes Panel 33 | AccessorFunc(PANEL, "bbp", "Bounded") -- takes Panel or true for parent 34 | 35 | melon.elements.Draggable = PANEL 36 | 37 | function PANEL:Init() 38 | self:SetBounded(true) 39 | self:SetAreaOf(self) 40 | self.dragging = false 41 | end 42 | 43 | function PANEL:OnMousePressed(m) 44 | if m == MOUSE_LEFT then 45 | self:StartDragging() 46 | self.dragging = {self:GetAreaOf():LocalCursorPos()} 47 | return 48 | end 49 | 50 | self.dragging = false 51 | end 52 | 53 | function PANEL:OnMouseReleased(m) 54 | if m == MOUSE_LEFT and self.dragging then 55 | self:EndDragging() 56 | self.dragging = false 57 | end 58 | end 59 | 60 | function PANEL:CalcDragPos() 61 | local x, y = gui.MouseX(), gui.MouseY() 62 | local xx,yy = self.dragging[1], self.dragging[2] 63 | 64 | return x - xx, y - yy 65 | end 66 | 67 | function PANEL:CalcCroppedPos() 68 | local x,y = self:CalcDragPos() 69 | local pw,ph = ( 70 | (ispanel(self:GetBounded()) and self:GetBounded()) or 71 | self:GetAreaOf():GetParent()):GetSize() 72 | local mw,mh = self:GetAreaOf():GetSize() 73 | 74 | return math.Clamp(x, 0, pw - mw), math.Clamp(y, 0, ph - mh) 75 | end 76 | 77 | function PANEL:Think() 78 | if not self.dragging then return end 79 | 80 | if not input.IsMouseDown(MOUSE_LEFT) or gui.IsConsoleVisible() or not system.HasFocus() then 81 | self:EndDragging() 82 | self.dragging = false 83 | 84 | return 85 | end 86 | 87 | local x,y = self:CalcDragPos() 88 | 89 | if self:GetBounded() then 90 | x, y = self:CalcCroppedPos() 91 | end 92 | 93 | self:GetAreaOf():SetPos(x, y) 94 | end 95 | 96 | ---- 97 | ---@method 98 | ---@name melon.elements.Draggable.StartDragging 99 | ---- 100 | ---- Called when the panel starts being dragged, remember to [SetCursor] 101 | ---- 102 | function PANEL:StartDragging() 103 | self:SetCursor("sizeall") 104 | end 105 | 106 | ---- 107 | ---@method 108 | ---@name melon.elements.Draggable.EndDragging 109 | ---- 110 | ---- Called when the panel stops being dragged, remember to [SetCursor] 111 | ---- 112 | function PANEL:EndDragging() 113 | self:SetCursor("arrow") 114 | end 115 | 116 | melon.DebugPanel("DPanel", function(p) 117 | p:SetSize(400, 400) 118 | p:Center() 119 | 120 | p.drag = vgui.Create("Melon:Draggable", p) 121 | p.drag:SetSize(200, 200) 122 | p.drag:Center() 123 | p.drag:SetAreaOf(p) 124 | end ) -------------------------------------------------------------------------------- /lua/melon/core/elements/cl_mask.lua: -------------------------------------------------------------------------------- 1 | 2 | melon.elements = melon.elements or {} 3 | 4 | ---- 5 | ---@panel Melon:Mask 6 | ---@name melon.elements.Mask 7 | ---- 8 | ---- A basepanel thats children render adheres to a mask 9 | ---- 10 | local PANEL = vgui.Register("Melon:Mask", {}, "Panel") 11 | melon.elements.Mask = PANEL 12 | 13 | local rt = GetRenderTargetEx("MelonMaskPanel", ScrW(), ScrH(), RT_SIZE_NO_CHANGE, MATERIAL_RT_DEPTH_SEPARATE, bit.bor(1, 256), 0, IMAGE_FORMAT_BGRA8888) 14 | local mat = CreateMaterial("MelonMaskPanel", "UnlitGeneric", { 15 | ["$basetexture"] = rt:GetName(), 16 | ["$translucent"] = "1", 17 | ["$vertexalpha"] = "1", 18 | ["$vertexcolor"] = "1", 19 | }) 20 | 21 | function PANEL:Init() 22 | self.exclude_from_mask = {} 23 | end 24 | 25 | function PANEL:Paint(w, h) 26 | render.PushRenderTarget(rt) 27 | cam.Start2D() 28 | render.Clear(0, 0, 0, 0) 29 | self:Mask(w, h) 30 | cam.End2D() 31 | render.PopRenderTarget() 32 | 33 | for k,v in pairs(self:GetChildren()) do 34 | if not IsValid(v) then continue end 35 | v:PaintManual() 36 | end 37 | end 38 | 39 | ---- 40 | ---@internal 41 | ---@method 42 | ---@name melon.elements.Mask:PaintMaskRelative 43 | ---- 44 | ---- Paints the mask texture relative to the given panel 45 | ---- 46 | function PANEL:PaintMaskRelative(s) 47 | local x,y = self:LocalToScreen(0,0) 48 | local sx, sy = s:LocalToScreen(-x, -y) 49 | surface.SetMaterial(mat) 50 | surface.DrawTexturedRect(-sx, -sy, ScrW(), ScrH()) 51 | end 52 | 53 | ---- 54 | ---@method 55 | ---@name melon.elements.Mask:Mask 56 | ---- 57 | ---@arg (w: number) Width of the panel 58 | ---@arg (h: number) Height of the panel 59 | ---- 60 | ---- Called once per frame to determine the mask of the panel, override this 61 | ---- 62 | function PANEL:Mask(w, h) end 63 | 64 | function PANEL:Exclude(panel, v) 65 | self.exclude_from_mask[panel] = (v == nil and true) or v 66 | end 67 | 68 | ---- 69 | ---@method 70 | ---@name melon.elements.Mask:MakeMask 71 | ---- 72 | ---@arg (panel: panel) Panel to add to this mask 73 | ---- 74 | ---- Adds a panel to this mask, must be called with no arguments in Init after child initialization 75 | ---- And this must be called on all children when added 76 | ---- 77 | ---- IMPORTANT, This function overrides Paint and OnChildAdded 78 | ---- 79 | function PANEL:MakeMask(panels, done, root) 80 | if ispanel(panels) then 81 | panels:SetPaintedManually(true) 82 | panels.OldPaint = panels.Paint 83 | panels.Paint = self.MaskPaint 84 | panels.MaskRoot = root or self 85 | 86 | panels.OnChildAdded = function(s) 87 | s.MaskUpdateRequested = true 88 | end 89 | 90 | return 91 | end 92 | 93 | done = done or {} 94 | panels = panels or self:GetChildren() 95 | 96 | for k, v in pairs(panels) do 97 | if done[v] then continue end 98 | if not IsValid(v) then continue end 99 | -- if v.OldPaint or v.Paint == self.MaskPaint then continue end 100 | 101 | done[v] = true 102 | 103 | self:MakeMask(v) 104 | self:MakeMask(v:GetChildren(), done, self) 105 | end 106 | end 107 | 108 | function PANEL:MaskPaint(w, h) 109 | if self.MaskUpdateRequested then 110 | self.MaskRoot:MakeMask(self:GetChildren(), nil, self.MaskRoot) 111 | end 112 | 113 | self.MaskRoot:DoMask(self, w, h) 114 | end 115 | 116 | ---- 117 | ---@method 118 | ---@name melon.elements.Mask:DoMask 119 | ---- 120 | ---@arg (pnl: Panel) Panel were rendering the mask of 121 | ---@arg (w: number) Width of the panel 122 | ---@arg (h: number) Height of the panel 123 | ---- 124 | ---- Called when a mask render has been requested 125 | ---- Override this if you wish to modify anything about how the mask is rendered, or use stacked masks 126 | ---- Remember, call pnl.OldPaint and self:PaintMaskRelative 127 | ---- 128 | function PANEL:DoMask(pnl, w, h) 129 | melon.masks.Start() 130 | pnl:OldPaint(w, h) 131 | melon.masks.Source() 132 | self:PaintMaskRelative(pnl) 133 | melon.masks.End() 134 | end 135 | 136 | melon.DebugPanel("Melon:Mask", function(p) 137 | local tl, bl, tr, br = 138 | vgui.Create("Panel", p), 139 | vgui.Create("Panel", p), 140 | vgui.Create("Panel", p), 141 | vgui.Create("Panel", p) 142 | 143 | tl.edge, bl.edge, tr.edge, br.edge = 144 | vgui.Create("DPanel", tl), 145 | vgui.Create("DPanel", bl), 146 | vgui.Create("DPanel", tr), 147 | vgui.Create("DPanel", br) 148 | 149 | function p:PerformLayout(w, h) 150 | tl:SetSize(w / 2, h / 2) 151 | bl:SetSize(w / 2, h / 2) 152 | tr:SetSize(w / 2, h / 2) 153 | br:SetSize(w / 2, h / 2) 154 | 155 | bl:SetPos(0, h / 2) 156 | tr:SetPos(w / 2, 0) 157 | br:SetPos(w / 2, h / 2) 158 | 159 | bl.edge:SetPos(0, bl:GetTall() - bl.edge:GetTall()) 160 | tr.edge:SetPos(tr:GetWide() - tr.edge:GetWide()) 161 | br.edge:SetPos(br:GetWide() - br.edge:GetWide(), br:GetTall() - br.edge:GetTall()) 162 | end 163 | 164 | tl.Paint = function(s,w,h) surface.SetDrawColor(melon.colors.Rainbow(45, 0)) surface.DrawRect(0, 0, w, h) end 165 | tr.Paint = function(s,w,h) surface.SetDrawColor(melon.colors.Rainbow(45, 20)) surface.DrawRect(0, 0, w, h) end 166 | bl.Paint = function(s,w,h) surface.SetDrawColor(melon.colors.Rainbow(45, 40)) surface.DrawRect(0, 0, w, h) end 167 | br.Paint = function(s,w,h) surface.SetDrawColor(melon.colors.Rainbow(45, 60)) surface.DrawRect(0, 0, w, h) end 168 | 169 | function p:Mask(w, h) 170 | local space = 30 171 | for i = -15, (math.ceil(w / space) - 4) / 2 do 172 | draw.NoTexture() 173 | surface.SetDrawColor(255, 255, 255) 174 | surface.DrawTexturedRectRotated(i * space + ((w / space) * i) - space, h / 2, w / space, h * 2, (CurTime() * 100) - 50) 175 | surface.DrawTexturedRectRotated(i * space + ((w / space) * i) - space, h / 2, w / space, h * 2, (CurTime() * 100) + 90) 176 | end 177 | end 178 | 179 | 180 | p:MakeMask() 181 | end ) -------------------------------------------------------------------------------- /lua/melon/core/elements/cl_resizeable.lua: -------------------------------------------------------------------------------- 1 | 2 | melon.elements = melon.elements or {} 3 | 4 | ---- 5 | ---@panel Melon:Resizable 6 | ---@name melon.elements.Resizable 7 | ---- 8 | ---- Resizable Panel object 9 | ---- 10 | ---` 11 | ---` local p = vgui.Create("Melon:Resizable") 12 | ---` p:SetSize(400, 400) 13 | ---` p:Center() 14 | ---` p:MakePopup() 15 | ---` 16 | ---` p:SetMaxSize(600, 600) 17 | ---` p:SetMinSize(200, 200) 18 | ---` 19 | local PANEL = vgui.Register("Melon:Resizable", {}, "EditablePanel") 20 | melon.elements.Resizable = PANEL 21 | 22 | function PANEL:Init() 23 | self:SetDragSize(12, 4) 24 | 25 | self.dragging = false 26 | end 27 | 28 | ---- 29 | ---@method 30 | ---@name melon.elements.Resizable.SetMaxSize 31 | ---- 32 | ---@arg (w: number) Width of the max size 33 | ---@arg (h: number) Height of the max size 34 | ---- 35 | ---- Sets the maximum size for the panel to be resizable to 36 | ---- 37 | function PANEL:SetMaxSize(w, h) 38 | self.maxw, self.maxh = w, h 39 | end 40 | 41 | ---- 42 | ---@method 43 | ---@name melon.elements.Resizable.SetMinSize 44 | ---- 45 | ---@arg (w: number) Width of the min size 46 | ---@arg (h: number) Height of the min size 47 | ---- 48 | ---- Sets the minimum size for the panel to be resizable to 49 | ---- 50 | function PANEL:SetMinSize(w, h) 51 | self.minw, self.minh = w, h 52 | end 53 | 54 | ---- 55 | ---@method 56 | ---@name melon.elements.Resizable.SetDragSize 57 | ---- 58 | ---@arg (size: number) Size of the draggable area 59 | ---@arg (pad: number) Padding from the edge of the draggable area 60 | ---- 61 | ---- Sets the draggable corner size and padding 62 | ---- 63 | function PANEL:SetDragSize(size, pad) 64 | self.dragsize = size 65 | self.dragpad = pad or size / 6 66 | end 67 | 68 | ---- 69 | ---@method 70 | ---@internal 71 | ---@name melon.elements.Resizable.WithinDragRegion 72 | ---- 73 | ---@arg (x: number) X to check 74 | ---@arg (y: number) Y to check 75 | ---@return (is: bool) Is it within the draggable region? 76 | ---- 77 | ---- Check if a coord is within the drag region 78 | ---- 79 | function PANEL:WithinDragRegion(x, y) 80 | local w, h = self:GetSize() 81 | local s = self.dragsize + self.dragpad 82 | 83 | return 84 | (x > (w - s) and x < w) and 85 | (y > (h - s) and y < h) 86 | end 87 | 88 | function PANEL:OnMousePressed(m) 89 | if m != MOUSE_LEFT then 90 | self.dragging = false 91 | return 92 | end 93 | 94 | local x,y = self:LocalCursorPos() 95 | if not self:WithinDragRegion(x, y) then 96 | self.dragging = false 97 | return 98 | end 99 | 100 | self:StartDragging() 101 | self.dragging = { 102 | x, y, 103 | self:GetSize() 104 | } 105 | end 106 | 107 | function PANEL:OnMouseReleased(m) 108 | if m == MOUSE_LEFT and self.dragging then 109 | self.dragging = false 110 | self:EndDragging() 111 | end 112 | end 113 | 114 | function PANEL:Think() 115 | if not self.dragging then return end 116 | 117 | if not input.IsMouseDown(MOUSE_LEFT) or gui.IsConsoleVisible() or not system.HasFocus() then 118 | self:EndDragging() 119 | self.dragging = false 120 | 121 | return 122 | end 123 | 124 | local x,y = self:LocalCursorPos() 125 | local xx,yy = self.dragging[1], self.dragging[2] 126 | local w,h = self.dragging[3], self.dragging[4] 127 | self:SetSize( 128 | math.Clamp(w - (xx - x), self.minw, self.maxw), 129 | math.Clamp(h - (yy - y), self.minh, self.maxh) 130 | ) 131 | end 132 | 133 | ---- 134 | ---@method 135 | ---@name melon.elements.Resizable.StartDragging 136 | ---- 137 | ---- Called when the panel starts being resized, remember to [SetCursor] 138 | ---- 139 | function PANEL:StartDragging() 140 | self:SetCursor("sizenwse") 141 | end 142 | 143 | ---- 144 | ---@method 145 | ---@name melon.elements.Resizable.EndDragging 146 | ---- 147 | ---- Called when the panel stops being resized, remember to [SetCursor] 148 | ---- 149 | function PANEL:EndDragging() 150 | self:SetCursor("arrow") 151 | end 152 | 153 | function PANEL:PaintOver(w, h) 154 | local size = self.dragsize 155 | local pad = self.dragpad 156 | surface.SetDrawColor(255, 255, 255, 255) 157 | melon.DrawImage("https://i.imgur.com/KRo8XD4.png", w - size - pad, h - size - pad, size, size) 158 | end 159 | 160 | melon.DebugPanel("Melon:Resizable", function(p) 161 | p:SetSize(400, 400) 162 | p:Center() 163 | 164 | p:SetMaxSize(600, 600) 165 | p:SetMinSize(200, 200) 166 | end ) -------------------------------------------------------------------------------- /lua/melon/core/elements/cl_tabs.lua: -------------------------------------------------------------------------------- 1 | 2 | melon.elements = melon.elements or {} 3 | 4 | ---- 5 | ---@panel Melon:Tabs 6 | ---@name melon.elements.Tabs 7 | ---- 8 | ---@accessor (ActiveTab: any) Active tab keyname 9 | ---@accessor (AnimTime: number) Animation time 10 | ---@accessor (InitialTab: any) First tab keyname 11 | ---- 12 | ---- Handles multiple tabs, think DPropertySheet without the visuals and builtin handling 13 | ---- 14 | local PANEL = vgui.Register("Melon:Tabs", {}, "EditablePanel") 15 | AccessorFunc(PANEL, "ActiveTab", "ActiveTab") 16 | AccessorFunc(PANEL, "AnimTime", "AnimTime", FORCE_NUMBER) 17 | AccessorFunc(PANEL, "InitialTab", "InitialTab") 18 | 19 | melon.elements.Tabs = PANEL 20 | 21 | function PANEL:Init() 22 | self.tabs = {} 23 | 24 | self:SetAnimTime(0.5) 25 | end 26 | 27 | ---- 28 | ---@method 29 | ---@name melon.elements.Tabs.AddTab 30 | ---- 31 | ---@arg (name: any) Keyname for the tab 32 | ---@arg (panel: Panel) Valid Panel to add to the tab handler 33 | ---@return (added: Panel) Panel that was added 34 | ---- 35 | ---- Adds the given panel as a tab with the name given to the handler 36 | ---- 37 | function PANEL:AddTab(name, pnl) 38 | self.tabs[name] = pnl 39 | pnl:SetParent(self) 40 | 41 | pnl:SetVisible(false) 42 | pnl:Dock(FILL) 43 | 44 | if not self:GetInitialTab() then 45 | self:SetInitialTab(name) 46 | self:SetTabPending(name) 47 | end 48 | 49 | return pnl 50 | end 51 | 52 | ---- 53 | ---@method 54 | ---@name melon.elements.Tabs.AddFutureTab 55 | ---- 56 | ---@arg (name: any) Identifier for the tab 57 | ---@arg (fn: func(self) -> tab) Function to run to generate the tab 58 | ---- 59 | ---- Adds a tab that will be generated when the tab gets switched to 60 | ---- 61 | function PANEL:AddFutureTab(name, fn) 62 | self.tabs[name] = fn 63 | 64 | if not self:GetInitialTab() then 65 | self:SetInitialTab(name) 66 | self:SetTabPending(name) 67 | end 68 | end 69 | 70 | ---- 71 | ---@method 72 | ---@name melon.elements.Tabs.SetTab 73 | ---- 74 | ---@arg (name: string) Name of the tab to set 75 | ---- 76 | ---- Sets the current tab to the given tab, animates! 77 | ---- 78 | function PANEL:SetTab(name) 79 | self.WantedTab = nil 80 | 81 | local old = self.tabs[self:GetActiveTab()] 82 | local new = self.tabs[name] 83 | 84 | if isfunction(new) then 85 | new = self:AddTab(name, new(self)) 86 | end 87 | 88 | if not new then return end 89 | 90 | if new == old then return end 91 | 92 | self:SetActiveTab(name) 93 | new:SetVisible(true) 94 | 95 | self.anim = { 96 | start = CurTime(), 97 | running = true, 98 | finished = false, 99 | progress = 0, 100 | 101 | old = old, 102 | new = new, 103 | } 104 | 105 | if new.OnTabSelected then 106 | new:OnTabSelected(old) 107 | end 108 | 109 | if old and old.OnTabDeselected then 110 | old:OnTabDeselected(new) 111 | end 112 | 113 | self:OnTabChanged(new, old) 114 | 115 | return new 116 | end 117 | 118 | ---- 119 | ---@method 120 | ---@name melon.elements.Tabs.SetTabPending 121 | ---- 122 | ---@arg (name: string) Name of the tab to set 123 | ---- 124 | ---- Sets the current tab to the given tab on the next tick 125 | ---- 126 | function PANEL:SetTabPending(name) 127 | self.WantedTab = name 128 | end 129 | 130 | ---- 131 | ---@method 132 | ---@internal 133 | ---@name melon.elements.Tabs.Think 134 | ---- 135 | ---- Handles animation progress stuff, dont touch, if you do touch replace it 136 | ---- 137 | function PANEL:Think() 138 | if self.WantedTab then 139 | self:SetTab(self.WantedTab) 140 | end 141 | 142 | if self.anim then 143 | self.anim.progress = Lerp((CurTime() - self.anim.start) / self:GetAnimTime(), self.anim.progress or 0, 1) 144 | 145 | if self.anim.progress >= 0.99 then 146 | self.anim.running = false 147 | self.anim.progress = 1 148 | self.anim.finished = true 149 | end 150 | end 151 | end 152 | 153 | ---- 154 | ---@method 155 | ---@internal 156 | ---@name melon.elements.Tabs.Paint 157 | ---- 158 | ---- Handles applying animation progress, read the source before replacing the Paint of this, Paint the parent instead 159 | ---- 160 | function PANEL:Paint() 161 | if not self.anim then return end 162 | if self.anim.old then 163 | self.anim.old:SetAlpha(255 - (255 * self.anim.progress)) 164 | end 165 | 166 | self.anim.new:SetAlpha(255 * self.anim.progress) 167 | 168 | if self.anim.finished then 169 | if self.anim.old then 170 | self:AnimDone(self.anim) 171 | self.anim.old:SetVisible(false) 172 | end 173 | 174 | self.anim = nil 175 | end 176 | end 177 | 178 | ---- 179 | ---@method 180 | ---@name melon.elements.Resizable.AnimDone 181 | ---- 182 | ---- Called when the animation is done, if you replace Paint you need to call this by hand 183 | ---- 184 | function PANEL:AnimDone() end 185 | 186 | ---- 187 | ---@method 188 | ---@name melon.elements.Resizable.OnTabChanged 189 | ---- 190 | ---@arg (new: panel) New tab panel 191 | ---@arg (old: panel) Old tab panel 192 | ---- 193 | ---- Called when the tab is changed 194 | ---- 195 | function PANEL:OnTabChanged(new, old) end 196 | 197 | function PANEL:PerformLayout(w, h) 198 | if self:GetActiveTab() then 199 | self.tabs[self:GetActiveTab()]:SetSize(w, h) 200 | end 201 | end 202 | 203 | melon.DebugPanel("Melon:Tabs", function(p) 204 | p:SetSize(400, 400) 205 | p:Center() 206 | 207 | local f = vgui.Create("DFrame", p:GetParent()) 208 | f:SetSize(200, 400) 209 | f:SetPos(p:GetX() - 205, p:GetY()) 210 | f:SetTitle("Select a tab") 211 | f:ShowCloseButton(false) 212 | 213 | f.scroll = vgui.Create("DScrollPanel", f) 214 | f.scroll:Dock(FILL) 215 | 216 | for i = 1, 10 do 217 | local tab = vgui.Create("DPanel", p) 218 | tab:SetBackgroundColor(ColorRand()) 219 | 220 | p:AddTab(i, tab) 221 | 222 | local btn = vgui.Create("DButton", f.scroll) 223 | btn:Dock(TOP) 224 | btn:SetText(i) 225 | btn:SetTextColor(tab:GetBackgroundColor()) 226 | 227 | btn.DoClick = function() 228 | p:SetTab(i) 229 | end 230 | end 231 | 232 | for i = 1, 10 do 233 | p:AddFutureTab("future" .. i, function(s) 234 | local v = vgui.Create("DButton", s) 235 | v:SetText("Generated @" .. CurTime()) 236 | return v 237 | end ) 238 | 239 | local btn = vgui.Create("DButton", f.scroll) 240 | btn:Dock(TOP) 241 | btn:SetText("future" .. i) 242 | 243 | btn.DoClick = function() 244 | p:SetTab("future" .. i) 245 | end 246 | end 247 | end ) -------------------------------------------------------------------------------- /lua/melon/core/elements/cl_text_entry.lua: -------------------------------------------------------------------------------- 1 | 2 | local REGISTRY = {} 3 | 4 | ---- 5 | ---@panel Melon:TextEntry 6 | ---- 7 | ---- Wrapper Panel to allow use of raw TextEntry's without painful manual 8 | ---- 9 | local PANEL = vgui.Register("Melon:TextEntry", {}, "TextEntry") 10 | 11 | function PANEL:Init() 12 | REGISTRY[self] = true 13 | self:SetPaintBackgroundEnabled(false) 14 | end 15 | 16 | hook.Add("VGUIMousePressed", "Melon:TextEntryFocus", function(panel) 17 | if not IsValid(panel) then return end 18 | if vgui.GetKeyboardFocus() and not REGISTRY[vgui.GetKeyboardFocus()] then return end 19 | if panel == vgui.GetKeyboardFocus() then return end 20 | 21 | while IsValid(panel) do 22 | if panel:HasHierarchicalFocus() then 23 | panel:KillFocus() 24 | return 25 | end 26 | 27 | panel = panel:GetParent() 28 | end 29 | end ) -------------------------------------------------------------------------------- /lua/melon/core/elements/panel_suite/cl_ps_main.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- This is all internal, so im not gonna bother documenting it 3 | local last = false 4 | local PANEL = vgui.Register("Melon:PanelSuite:Main", {}, "EditablePanel") 5 | AccessorFunc(PANEL, "SuitePanelType", "SuitePanelType") 6 | AccessorFunc(PANEL, "SuiteFunction", "SuiteFunction") 7 | AccessorFunc(PANEL, "SuitePanel", "SuitePanel") 8 | 9 | function PANEL:Init() 10 | self.allowed_to_draw = false 11 | self:MakePopup() 12 | 13 | self.text_color = Color(255, 255, 255, 50) 14 | 15 | self.close = vgui.Create("Melon:Button", self) 16 | self.close:SetPaintedManually(true) 17 | self.close.Paint = function(s,w,h) 18 | local xw = h * .5 19 | local xh = melon.Scale(4) 20 | 21 | surface.SetDrawColor(melon.PanelDevSuite.Theme.Background) 22 | surface.DrawRect(0, 0, w, h) 23 | 24 | draw.NoTexture() 25 | surface.SetDrawColor(255, 255, 255) 26 | surface.DrawTexturedRectRotated(w / 2, h / 2, xw - xh / 2, xh / 2, 45) 27 | surface.DrawTexturedRectRotated(w / 2, h / 2, xw - xh / 2, xh / 2, -45) 28 | 29 | local oc = DisableClipping(true) 30 | surface.SetDrawColor(melon.colors.Rainbow(s:IsHovered() and 1000 or 20)) 31 | surface.DrawOutlinedRect(-2, -2, w + 4, h + 4, 2) 32 | DisableClipping(oc) 33 | end 34 | self.close.LeftClick = function() 35 | self:Remove() 36 | end 37 | 38 | self.toolbtn = vgui.Create("Melon:Button", self) 39 | self.toolbtn:SetPaintedManually(true) 40 | self.toolbtn.Paint = function(s,w,h) 41 | surface.SetDrawColor(melon.PanelDevSuite.Theme.Background) 42 | surface.DrawRect(0, 0, w, h) 43 | 44 | surface.SetDrawColor(255, 255, 255) 45 | local size = h * .45 46 | melon.DrawImageRotated("https://i.imgur.com/v2PAI0a.png", w / 2, h / 2, size, size, 0) 47 | 48 | local oc = DisableClipping(true) 49 | surface.SetDrawColor(melon.colors.Rainbow(s:IsHovered() and 1000 or 20)) 50 | surface.DrawOutlinedRect(-2, -2, w + 4, h + 4, 2) 51 | DisableClipping(oc) 52 | end 53 | self.toolbtn.LeftClick = function() 54 | self:ToggleTools() 55 | end 56 | end 57 | 58 | function PANEL:PerformLayout(w, h) 59 | self.close:SetSize(melon.Scale(50), melon.Scale(30)) 60 | self.close:SetPos(w - self.close:GetWide(), 0) 61 | 62 | self.toolbtn:SetSize(melon.Scale(50), melon.Scale(30)) 63 | self.toolbtn:SetPos(w - self.close:GetWide() - self.close:GetWide() - melon.Scale(7), 0) 64 | end 65 | 66 | -- function PANEL:Think() 67 | -- if input.IsKeyDown(KEY_F2) and input.IsShiftDown() then 68 | -- if self.pressing then return end 69 | -- self.pressing = true 70 | 71 | -- if self.allowed_to_draw then 72 | -- self.allowed_to_draw = false 73 | -- return 74 | -- end 75 | 76 | -- self.allowed_to_draw = { 77 | -- ["CHudGMod"] = true 78 | -- } 79 | -- else 80 | -- self.pressing = false 81 | -- end 82 | 83 | -- if input.IsKeyDown(KEY_F3) and input.IsShiftDown() then 84 | -- if self.pressing2 then return end 85 | -- self.pressing2 = true 86 | 87 | -- self:ToggleTools() 88 | -- else 89 | -- self.pressing2 = false 90 | -- end 91 | -- end 92 | 93 | function PANEL:Paint(w, h) 94 | if self.allowed_to_draw then return end 95 | -- local _, th = draw.Text({ 96 | -- text = "Press Shift+F2 to hide display", 97 | -- pos = {melon.ScaleN(10, 10)}, 98 | -- font = melon.Font(18), 99 | -- xalign = 0, 100 | -- yalign = 0, 101 | -- color = self.text_color 102 | -- }) 103 | 104 | -- draw.Text({ 105 | -- text = "Press Shift+F3 to show tools", 106 | -- pos = {melon.Scale(10), melon.Scale(10) + th}, 107 | -- font = melon.Font(18), 108 | -- xalign = 0, 109 | -- yalign = 0, 110 | -- color = self.text_color 111 | -- }) 112 | 113 | self.close:PaintManual() 114 | self.toolbtn:PaintManual() 115 | 116 | if IsValid(self.tools) then 117 | self.tools:PaintManual() 118 | end 119 | 120 | self.close:SetZPos(32766) 121 | 122 | if self:IsMouseInputEnabled() then 123 | return 124 | end 125 | 126 | draw.Text({ 127 | text = "shift+L to close debugger", 128 | pos = {w / 2, self.close:GetTall() / 2}, 129 | xalign = 1, 130 | yalign = 1, 131 | font = melon.Font(20), 132 | color = {r = 255, g = 255, b = 255, a = 20} 133 | }) 134 | 135 | if input.IsKeyDown(KEY_L) and input.IsShiftDown() then 136 | self:Remove() 137 | end 138 | end 139 | 140 | function PANEL:OnMousePressed(m) 141 | if m == MOUSE_MIDDLE then 142 | self:Remove() 143 | end 144 | end 145 | 146 | function PANEL:OnRemove() 147 | if IsValid(self:GetSuitePanel()) then 148 | self:GetSuitePanel():Remove() 149 | end 150 | end 151 | 152 | function PANEL:SuiteReady() 153 | local pnl 154 | if isstring(self:GetSuitePanelType() or "DPanel") then 155 | pnl = vgui.Create(self:GetSuitePanelType() or "DPanel", self) 156 | else 157 | pnl = vgui.CreateFromTable(self:GetSuitePanelType(), self) 158 | end 159 | 160 | self:SetSuitePanel(pnl) 161 | 162 | pnl:SetSize(500, 500) 163 | pnl:Center() 164 | 165 | if self:GetSuiteFunction() then 166 | self:GetSuiteFunction()(pnl) 167 | end 168 | 169 | if last then 170 | self:ToggleTools(true) 171 | end 172 | end 173 | 174 | function PANEL:ToggleTools(t) 175 | if not t then 176 | last = not last 177 | end 178 | 179 | if IsValid(self.tools) then 180 | self.tools:Remove() 181 | return 182 | end 183 | 184 | self.tools = vgui.Create("Melon:PanelSuite:Tools", self) 185 | self.tools:SetSuitePanel(self:GetSuitePanel()) 186 | end 187 | 188 | melon.DebugPanel2__TEST() 189 | -------------------------------------------------------------------------------- /lua/melon/core/elements/panel_suite/cl_ps_nav.lua: -------------------------------------------------------------------------------- 1 | return 123 -------------------------------------------------------------------------------- /lua/melon/core/elements/panel_suite/cl_ps_tools.lua: -------------------------------------------------------------------------------- 1 | 2 | local PANEL = vgui.Register("Melon:PanelSuite:Tools", {}, "Panel") 3 | AccessorFunc(PANEL, "SuitePanel", "SuitePanel") 4 | 5 | function PANEL:Init() 6 | self:SetSize(melon.Scale(400), melon.Scale(700)) 7 | self:SetPos(melon.Scale(10), 0) 8 | self:CenterVertical() 9 | 10 | self.topbar = vgui.Create("Melon:PanelSuite:Topbar", self) 11 | self.topbar:SetAreaOf(self) 12 | self.tabs = vgui.Create("Melon:Tabs", self) 13 | 14 | for _, v in pairs(melon.PanelDevSuite.Tabs) do 15 | self.topbar:AddTabButton(v[1]) 16 | self.tabs:AddTab(v[1], vgui.Create(v[2], self.tabs)) 17 | end 18 | 19 | self.topbar:SetTabPanel(self.tabs) 20 | 21 | function self.tabs.OnTabChanged(s, new) 22 | if not IsValid(new) then return end 23 | if not IsValid(self:GetSuitePanel()) then return end 24 | 25 | if new.Ready then 26 | new:Ready(self:GetSuitePanel()) 27 | else 28 | print("missing ready") 29 | end 30 | end 31 | 32 | self.opened_from = {} 33 | end 34 | 35 | function PANEL:SanitizePath(path) 36 | return path 37 | :gsub("^addons/", "") 38 | :gsub("^melonlib/", "") 39 | :gsub("^lua/", "") 40 | :gsub("^melon/", "") 41 | end 42 | 43 | function PANEL:SetSuitePanel(pnl) 44 | self.SuitePanel = pnl 45 | self.tabs.tabs[self.tabs:GetActiveTab()]:Ready(pnl) 46 | 47 | self.opened_from = {} 48 | local init, paint = (pnl.Init and debug.getinfo(pnl.Init) or {}), (pnl.Paint and debug.getinfo(pnl.Paint) or {}) 49 | 50 | init = self:SanitizePath(init.short_src or "") 51 | paint = self:SanitizePath(paint.short_src or "") 52 | 53 | if not string.StartsWith(init, "derma/") then 54 | table.insert(self.opened_from, init) 55 | end 56 | 57 | if not string.StartsWith(paint, "derma/") then 58 | table.insert(self.opened_from, paint) 59 | end 60 | 61 | if init == paint and #self.opened_from == 2 then 62 | self.opened_from = {init} 63 | end 64 | end 65 | 66 | function PANEL:PerformLayout(w, h) 67 | self.topbar:Dock(TOP) 68 | self.topbar:SetTall(melon.Scale(30)) 69 | self.tabs:Dock(FILL) 70 | end 71 | function PANEL:Paint(w, h) 72 | surface.SetDrawColor(melon.PanelDevSuite.Theme.Background) 73 | surface.DrawRect(0, 0, w, h) 74 | end 75 | 76 | function PANEL:PaintOver(w, h) 77 | local oc = DisableClipping(true) 78 | surface.SetDrawColor(melon.PanelDevSuite.Theme.Accent) 79 | surface.DrawOutlinedRect(-1, -1, w + 2, h + 2) 80 | 81 | local y = h + 4 82 | 83 | for k,v in pairs(self.opened_from) do 84 | local _, th = draw.TextShadow({ 85 | text = v, 86 | pos = {w / 2, y}, 87 | xalign = 1, 88 | yalign = 3, 89 | font = melon.Font(12, "Inter"), 90 | color = {r = 255, g = 255, b = 255, a = 60} 91 | }, 2, 100) 92 | 93 | y = y + th 94 | end 95 | DisableClipping(oc) 96 | end 97 | 98 | melon.DebugPanel2__TEST() -------------------------------------------------------------------------------- /lua/melon/core/elements/panel_suite/cl_ps_topbar.lua: -------------------------------------------------------------------------------- 1 | 2 | local PANEL = vgui.Register("Melon:PanelSuite:Topbar", {}, "Melon:Draggable") 3 | AccessorFunc(PANEL, "TabPanel", "TabPanel") 4 | 5 | function PANEL:Init() 6 | self.tabs = {} 7 | self.lhs = vgui.Create("Panel", self) 8 | self.rhs = vgui.Create("Panel", self) 9 | 10 | self:AddRhsButton(function(s,w,h) 11 | draw.NoTexture() 12 | surface.SetDrawColor(s.Color) 13 | surface.DrawTexturedRectRotated(w / 2, h / 2, w / 2, melon.Scale(2), 45) 14 | surface.DrawTexturedRectRotated(w / 2, h / 2, w / 2, melon.Scale(2), -45) 15 | end, function() 16 | self:GetParent():Remove() 17 | end ) 18 | 19 | self:AddRhsButton(function(s,w,h) 20 | draw.NoTexture() 21 | surface.SetDrawColor(s.Color) 22 | melon.DrawImageRotated("https://i.imgur.com/IOyRtM7.png", w / 2, h / 2, w / 2, h / 2) 23 | end, function() 24 | self:GetTabPanel():SetTab("settings") 25 | end ) 26 | 27 | self.lhs.Think = self.LHSThink 28 | self.lhs.PerformLayout = self.LHSPerformLayout 29 | 30 | function self.lhs:OnMouseWheeled(d) 31 | self.WantedScrollX = self.WantedScrollX + (d * 50) 32 | end 33 | 34 | function self.lhs:PaintOver(w,h) 35 | local active_i_hate_you_tbh = self:GetParent():GetTabPanel():GetActiveTab() or "" 36 | local btn = self:GetParent().tabs[active_i_hate_you_tbh] 37 | 38 | if btn then 39 | self.accentX = Lerp(FrameTime() * 10, self.accentX or btn:GetX(), btn:GetX()) 40 | self.accentW = Lerp(FrameTime() * 10, self.accentW or btn:GetWide(), btn:GetWide()) 41 | self.accentH = Lerp(FrameTime() * 10, self.accentH or 3, melon.Scale(3)) 42 | else 43 | self.accentH = Lerp(FrameTime() * 10, self.accentH or 0, 0) 44 | end 45 | 46 | surface.SetDrawColor(melon.PanelDevSuite.Theme.Accent) 47 | surface.DrawRect(self.accentX, h - self.accentH, self.accentW, self.accentH) 48 | 49 | self.RealItemW = self.RealItemW or 0 50 | if self.RealItemW < w then return end 51 | local pos = math.abs(self.ScrollX) / (self.RealItemW - w) 52 | 53 | local shadow = melon.PanelDevSuite.Theme.Shadow 54 | surface.SetMaterial(melon.Material("vgui/gradient-l")) 55 | surface.SetDrawColor(shadow.r, shadow.g, shadow.b, pos * 255) 56 | surface.DrawTexturedRect(0, 0, h, h) 57 | 58 | surface.SetMaterial(melon.Material("vgui/gradient-r")) 59 | surface.SetDrawColor(shadow.r, shadow.g, shadow.b, 255 - (pos * 255)) 60 | surface.DrawTexturedRect(w - h, 0, h, h) 61 | end 62 | end 63 | 64 | function PANEL:AddTabButton(text) 65 | local btn = vgui.Create("DButton", self.lhs) 66 | self.tabs[text] = btn 67 | btn:SetText("") 68 | btn.text = text 69 | 70 | btn.Color = melon.colors.CopyShallow(melon.PanelDevSuite.Theme.SecondaryText) 71 | btn.Paint = function(s, w, h) 72 | draw.Text({ 73 | text = s.text, 74 | pos = {w / 2, h / 2}, 75 | xalign = 1, 76 | yalign = 1, 77 | font = melon.Font(26), 78 | color = s.Color 79 | }) 80 | end 81 | btn.Think = function(s) 82 | melon.colors.LerpMod(FrameTime() * 10, s.Color, s:IsHovered() and melon.PanelDevSuite.Theme.Text or melon.PanelDevSuite.Theme.SecondaryText) 83 | end 84 | btn.DoClick = function(s) 85 | self:GetTabPanel():SetTab(text) 86 | end 87 | 88 | return btn 89 | end 90 | 91 | function PANEL:AddRhsButton(paint, fn) 92 | local btn = vgui.Create("DButton", self.rhs) 93 | btn.Paint = paint 94 | btn.DoClick = fn 95 | btn:SetText("") 96 | btn:Dock(RIGHT) 97 | 98 | btn.Color = melon.colors.CopyShallow(melon.PanelDevSuite.Theme.SecondaryText) 99 | btn.Think = function(s) 100 | melon.colors.LerpMod(FrameTime() * 10, s.Color, s:IsHovered() and melon.PanelDevSuite.Theme.Text or melon.PanelDevSuite.Theme.SecondaryText) 101 | end 102 | end 103 | 104 | function PANEL:LHSThink() 105 | self.WantedScrollX = self.WantedScrollX or 0 106 | self.ScrollX = self.ScrollX or 0 107 | 108 | local iw = 0 109 | for _,v in pairs(self:GetChildren()) do 110 | if not v.RealX then return end 111 | v:SetPos(v.RealX + self.ScrollX, 0) 112 | iw = iw + v:GetWide() 113 | end 114 | 115 | self.RealItemW = iw 116 | self.WantedScrollX = Lerp(FrameTime() * 10, self.WantedScrollX, math.Clamp(self.WantedScrollX, -(iw - self:GetWide()), 0)) 117 | self.ScrollX = Lerp(FrameTime() * 10, self.ScrollX, self.WantedScrollX) 118 | end 119 | 120 | function PANEL:LHSPerformLayout(w, h) 121 | surface.SetFont(melon.Font(26)) 122 | 123 | self.RealItemW = 0 124 | for k,v in pairs(self:GetChildren()) do 125 | local tw = surface.GetTextSize(v.text) 126 | v:SetSize(tw + melon.Scale(20), h) 127 | v.RealX = self.RealItemW 128 | self.RealItemW = self.RealItemW + v:GetWide() 129 | 130 | if not self.FinishedFirstLayout then 131 | v:SetPos(v.RealX, 0) 132 | end 133 | end 134 | 135 | self.FinishedFirstLayout = true 136 | end 137 | 138 | function PANEL:PerformLayout(w, h) 139 | self.rhs:SetSize(#self.rhs:GetChildren() * h, h) 140 | self.rhs:SetPos(w - self.rhs:GetWide(), 0) 141 | 142 | for k,v in pairs(self.rhs:GetChildren()) do 143 | v:SetWide(h) 144 | end 145 | 146 | self.lhs:SetSize(w - self.rhs:GetWide(), h) 147 | end 148 | 149 | function PANEL:Paint(w, h) 150 | surface.SetDrawColor(melon.PanelDevSuite.Theme.Foreground) 151 | surface.DrawRect(0, 0, w, h) 152 | end 153 | 154 | melon.DebugPanel2__TEST() -------------------------------------------------------------------------------- /lua/melon/core/elements/panel_suite/cl_ps_tree.lua: -------------------------------------------------------------------------------- 1 | 2 | local dockRot = {} 3 | dockRot[LEFT] = 0 4 | dockRot[RIGHT] = 180 5 | dockRot[TOP] = -90 6 | dockRot[BOTTOM] = 90 7 | 8 | local PANEL = vgui.Register("Melon:PanelSuite:Tree", {}, "Panel") 9 | AccessorFunc(PANEL, "LineH", "LineH") 10 | AccessorFunc(PANEL, "FontSize", "FontSize") 11 | AccessorFunc(PANEL, "DrawY", "DrawY") 12 | AccessorFunc(PANEL, "ActiveNode", "ActiveNode") 13 | 14 | function PANEL:Init() 15 | self.node_count = 0 16 | self.node_state = {} 17 | self.children = {} 18 | self:SetLineH(30) 19 | self:SetFontSize(20) 20 | self:SetDrawY(0) 21 | self.RealDrawY = 0 22 | end 23 | 24 | function PANEL:OnMouseWheeled(d) 25 | self:SetDrawY(self:GetDrawY() + (d * melon.Scale(20))) 26 | end 27 | 28 | function PANEL:Node(panel, real_panel) 29 | if not real_panel then 30 | real_panel = self 31 | self:Init() 32 | end 33 | 34 | local node = { 35 | panel = panel, 36 | children = {}, 37 | Node = function(s, p) 38 | return self.Node(s, p, real_panel) 39 | end 40 | } 41 | real_panel.node_count = real_panel.node_count + 1 42 | 43 | table.insert(self.children, node) 44 | return node 45 | end 46 | 47 | function PANEL:OnNodeHover(info) 48 | if not info then 49 | self:GetParent():SetActivePanel(false) 50 | return 51 | end 52 | 53 | if self.hovering == info.node then return end 54 | self.hovering = info.node 55 | 56 | self:GetParent():SetActivePanel({ 57 | panel = info.node.panel, 58 | x = info.x, 59 | y = info.y 60 | }) 61 | end 62 | 63 | function PANEL:OnMousePressed(m) 64 | if m == MOUSE_LEFT then 65 | self.clicked = true 66 | end 67 | end 68 | 69 | function PANEL:IsLineHovered(y) 70 | if not self:IsMouseInputEnabled() then return false end 71 | 72 | local lineh = melon.Scale(self:GetLineH()) 73 | if self.cursorx < 0 or self.cursorx > self:GetWide() then 74 | return false 75 | end 76 | return (self.cursory > y) and (self.cursory < y + lineh) 77 | end 78 | 79 | function PANEL:CalcClick(node) 80 | if node == self:GetActiveNode() then 81 | return 82 | end 83 | 84 | self:GetParent():SetActiveNode(node) 85 | end 86 | 87 | function PANEL:PaintNode(node, x, y, w, h) 88 | local lineh = melon.Scale(self:GetLineH()) 89 | local marginx = melon.Scale(20) 90 | 91 | -- this is extraordinarily performant 92 | -- we can have THOUSANDS of children with NO performance loss!!!!!!!!!! 93 | if y > h then return y end 94 | if y + lineh < 0 then 95 | for _, n in pairs(node.children) do 96 | y = self:PaintNode(n, x + marginx, y + lineh, w, h) 97 | end 98 | 99 | return y 100 | end 101 | 102 | local hov = self:IsLineHovered(y) 103 | local v = self.node_state[node] 104 | if self.ActiveNode == node then 105 | self.node_state[node] = Lerp(FrameTime() * 10, v or 0, 1) 106 | elseif hov then 107 | self.node_state[node] = Lerp(FrameTime() * 10, v or 0, 0.6) 108 | 109 | local xx, yy = self:LocalToScreen(w - (lineh / 2), y + lineh / 2) 110 | self.hovering = { 111 | node = node, 112 | x = xx, 113 | y = yy 114 | } 115 | else 116 | self.node_state[node] = Lerp(FrameTime() * 10, v or 0, 0) 117 | 118 | if self.node_state[node] < 0.01 then 119 | self.node_state[node] = nil 120 | v = nil 121 | end 122 | end 123 | 124 | if self.clicked and hov then 125 | self:CalcClick(node) 126 | 127 | self.clicked = false 128 | end 129 | 130 | local markerh = melon.Scale(2) 131 | surface.SetDrawColor(melon.PanelDevSuite.Theme.Midground) 132 | surface.DrawRect(marginx / 2, y + lineh / 2 - (markerh / 2), x, markerh) 133 | 134 | local accent = melon.PanelDevSuite.Theme.Accent 135 | if not IsValid(node.panel) then 136 | accent = melon.PanelDevSuite.Theme.BadAccent 137 | end 138 | 139 | if v then 140 | surface.SetDrawColor(accent.r, accent.g, accent.b, self.node_state[node] * 255) 141 | surface.DrawRect(0, y, w, lineh) 142 | surface.SetDrawColor(255, 255, 255, self.node_state[node] * 20) 143 | surface.DrawRect(0, y, w, melon.Scale(2)) 144 | surface.DrawRect(0, y + lineh - melon.Scale(2), w, melon.Scale(2)) 145 | surface.DrawRect(marginx / 2, y + lineh / 2 - (markerh / 2), x, markerh) 146 | end 147 | 148 | draw.Text({ 149 | text = node.panel.ClassName or node.panel:GetClassName(), 150 | pos = {marginx + x, y + lineh / 2}, 151 | xalign = 0, 152 | yalign = 1, 153 | font = melon.Font(self:GetFontSize()), 154 | color = melon.PanelDevSuite.Theme.Text 155 | }) 156 | 157 | if node.panel:GetDock() == 0 then 158 | local pnl_x, pnl_y = node.panel:GetPos() 159 | local pnl_w, pnl_h = node.panel:GetSize() 160 | draw.Text({ 161 | text = "[" .. pnl_x .. "," .. pnl_y .. " | " .. pnl_w .. "x" .. pnl_h .. "]", 162 | pos = {w - marginx, y + lineh / 2}, 163 | xalign = 2, 164 | yalign = 1, 165 | font = melon.Font(self:GetFontSize()), 166 | color = melon.PanelDevSuite.Theme.SecondaryText 167 | }) 168 | else 169 | local dock = node.panel:GetDock() 170 | local boxs = lineh * .8 171 | surface.SetDrawColor(melon.PanelDevSuite.Theme.SecondaryText) 172 | 173 | if dock == FILL then 174 | surface.DrawRect(w - marginx - boxs, y + lineh / 2 - boxs / 2, boxs, boxs) 175 | else 176 | surface.SetMaterial(melon.Material("vgui/gradient-l")) 177 | surface.DrawOutlinedRect(w - marginx - boxs, y + lineh / 2 - boxs / 2, boxs, boxs, 2) 178 | surface.DrawTexturedRectRotated(w - marginx - boxs / 2, y + lineh / 2, boxs - 4, boxs - 4, dockRot[dock]) 179 | end 180 | end 181 | 182 | for _, n in pairs(node.children) do 183 | y = self:PaintNode(n, x + marginx, y + lineh, w, h) 184 | end 185 | 186 | return y 187 | end 188 | 189 | function PANEL:PerformLayout(w, h) 190 | self.total_h = self.node_count * melon.Scale(self:GetLineH()) 191 | end 192 | 193 | function PANEL:Paint(w, h) 194 | self.hovering = false 195 | self.cursorx, self.cursory = self:LocalCursorPos() 196 | 197 | if (not self.children[1]) or (not IsValid(self.children[1].panel)) then 198 | draw.Text({ 199 | text = "No Nodes", 200 | pos = {w / 2, h / 2}, 201 | xalign = 1, 202 | yalign = 1, 203 | font = melon.Font(50), 204 | color = melon.PanelDevSuite.Theme.Foreground, 205 | }) 206 | 207 | return 208 | end 209 | 210 | local th = math.max(self.total_h or 0, h) 211 | self.RealDrawY = Lerp(FrameTime() * 10, self.RealDrawY or 0, self:GetDrawY()) 212 | self.DrawY = Lerp(FrameTime() * 10, self.DrawY, -math.Clamp(math.abs(self.DrawY), 0, th - h)) 213 | self:PaintNode(self.children[1], 0, self.RealDrawY, w, h) 214 | 215 | if self:GetActiveNode() then 216 | local xx, yy = self:LocalToScreen(w, h / 2) 217 | 218 | self:OnNodeHover({ 219 | node = self:GetActiveNode(), 220 | x = xx, 221 | y = yy 222 | }) 223 | return 224 | end 225 | 226 | self:OnNodeHover(self.hovering) 227 | self.clicked = false 228 | end 229 | 230 | function PANEL:PaintOver(w, h) 231 | 232 | end 233 | 234 | melon.DebugPanel2__TEST() -------------------------------------------------------------------------------- /lua/melon/core/elements/panel_suite/tabs/cl_pst_renderview.lua: -------------------------------------------------------------------------------- 1 | --@TODO 2 | -- Hook EVERY render function here and track it, then we can display everything right :) 3 | 4 | local _PANEL = vgui.Register("Melon:PanelSuite:Tab:RenderView", {}, "DPanel") 5 | 6 | melon.DebugPanel2__TEST() -------------------------------------------------------------------------------- /lua/melon/core/elements/panel_suite/tabs/cl_pst_settings.lua: -------------------------------------------------------------------------------- 1 | return 123 -------------------------------------------------------------------------------- /lua/melon/core/elements/scroller/cl_scrollbar.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@panel Melon:ScrollBar 4 | ---@name melon.elements.ScrollBar 5 | ---- 6 | ---@accessor (ScrollAmount: number) Current scroll pos in pixels 7 | ---@accessor (Canvas: Panel) Current canvas panel 8 | ---@accessor (Horizontal: boolean) Is the scrollbar horizontal 9 | ---@accessor (OverflowAmount: number) How much is the scrollbar currently overflowing, negative if up 10 | ---@accessor (iRealScrollAmount: number) Internal, current real scroll position, interpolated 11 | ---@accessor (iGrabbed: table) Internal, grab data 12 | ---@accessor (iGripColor: Color) Internal, grip color 13 | ---@accessor (iPanning: boolean) Internal, are we panning? 14 | ---- 15 | ---- Scrollbar for ScrollPanels, handles the actual scrolling logic 16 | ---- 17 | local PANEL = vgui.Register("Melon:ScrollBar", {}, "Panel") 18 | AccessorFunc(PANEL, "ScrollAmount", "ScrollAmount") 19 | AccessorFunc(PANEL, "Canvas", "Canvas") 20 | AccessorFunc(PANEL, "Horizontal", "Horizontal") 21 | AccessorFunc(PANEL, "OverflowAmount", "OverflowAmount") 22 | AccessorFunc(PANEL, "iRealScrollAmount", "iRealScrollAmount") 23 | AccessorFunc(PANEL, "iGrabbed", "iGrabbed") 24 | AccessorFunc(PANEL, "iGripColor", "iGripColor") 25 | AccessorFunc(PANEL, "iPanning", "iPanning") 26 | 27 | function PANEL:Init() 28 | self.Grip = vgui.Create("Panel", self) 29 | 30 | self:SetScrollAmount(0) 31 | self:SetiRealScrollAmount(0) 32 | self:SetiGripColor(Color(0, 0, 0, 160)) 33 | self:SetiPanning(false) 34 | 35 | function self.Grip.OnMousePressed(s, m) 36 | if m == MOUSE_LEFT then 37 | self:Grab() 38 | end 39 | end 40 | end 41 | 42 | function PANEL:Paint(w, h) 43 | if self:GetDomain() == 0 then return end 44 | 45 | local amt = math.Clamp(math.abs(self:GetOverflowAmount() or 0), 0, (self:GetHorizontal() and h or w) * 2) 46 | 47 | melon.masks.Start() 48 | draw.RoundedBox(melon.Scale(4), self.Grip:GetX(), self.Grip:GetY(), self.Grip:GetWide(), self.Grip:GetTall(), self:GetiGripColor()) 49 | melon.masks.Source() 50 | surface.SetDrawColor(255,255,255) 51 | 52 | if self:GetHorizontal() then 53 | surface.SetMaterial(melon.Material("vgui/gradient-l")) 54 | surface.DrawTexturedRect(0, 0, amt, h) 55 | surface.SetMaterial(melon.Material("vgui/gradient-r")) 56 | surface.DrawTexturedRect(w - amt + 1, 0, amt, h) 57 | else 58 | surface.SetMaterial(melon.Material("vgui/gradient-u")) 59 | surface.DrawTexturedRect(0, 0, w, amt) 60 | surface.SetMaterial(melon.Material("vgui/gradient-d")) 61 | surface.DrawTexturedRect(0, h - amt + 1, w, amt) 62 | end 63 | 64 | melon.masks.End(melon.masks.KIND_STAMP) 65 | end 66 | 67 | function PANEL:OnMousePressed(m) 68 | if m == MOUSE_MIDDLE and self:GetParent():GetPanningEnabled() then 69 | self:GetParent():Pan() 70 | end 71 | 72 | if m != MOUSE_LEFT then return end 73 | 74 | local x, y = self:LocalCursorPos() 75 | local r = self:GetHorizontal() and (x / self:GetWide()) or (y / self:GetTall()) 76 | 77 | self:SetScrollAmount(math.Clamp(r, 0, 1) * (self:GetHorizontal() and self:GetCanvas():GetWide() or self:GetCanvas():GetTall()) - ((self:GetHorizontal() and self.Grip:GetWide() or self.Grip:GetTall()) / 2)) 78 | end 79 | 80 | function PANEL:PerformLayout(w, h) 81 | if not IsValid(self:GetCanvas()) then return end 82 | 83 | local cvs = self:GetCanvas() 84 | 85 | local sw, sh = self:GetParent():GetSize() 86 | local cw, ch = cvs:GetSize() 87 | 88 | if self:GetHorizontal() then 89 | self.Grip:SetSize(w * (sw / cw), h) 90 | else 91 | self.Grip:SetSize(w, h * (sh / ch)) 92 | end 93 | end 94 | 95 | function PANEL:GrabThink() 96 | local grab = self:GetiGrabbed() 97 | 98 | if grab then 99 | if (not self:GetiPanning() and not input.IsMouseDown(MOUSE_LEFT)) then 100 | self:SetiGrabbed(nil) 101 | elseif self:GetiPanning() and (not input.IsMouseDown(MOUSE_MIDDLE)) then 102 | self:SetiPanning(false) 103 | self:SetiGrabbed(nil) 104 | end 105 | 106 | local nx, ny = self:LocalCursorPos() 107 | local pos = self:GetHorizontal() and (nx - grab.x) or (ny - grab.y) 108 | local dom = self:GetHorizontal() and (self:GetWide() - self.Grip:GetWide()) or (self:GetTall() - self.Grip:GetTall()) 109 | 110 | self:SetScrollAmount( 111 | math.Clamp( 112 | (pos / dom) * self:GetDomain(), 113 | -self:GetParent():GetScrollbarMaxOverflow(), 114 | self:GetDomain() + self:GetParent():GetScrollbarMaxOverflow() 115 | ) 116 | ) 117 | end 118 | end 119 | 120 | function PANEL:ScrollThink() 121 | if not self:GetCanvas() then return end 122 | if self:GetDomain() == 0 then return end 123 | 124 | self:GrabThink() 125 | 126 | self:SetScrollAmount( 127 | Lerp(FrameTime() * 10, self:GetScrollAmount(), math.Clamp(self:GetScrollAmount(), 0, self:GetDomain())) 128 | ) 129 | 130 | local rounded = math.Round(self:GetScrollAmount()) 131 | if rounded == 0 then 132 | self:SetScrollAmount(0) 133 | elseif rounded == self:GetDomain() then 134 | self:SetScrollAmount(self:GetDomain()) 135 | end 136 | 137 | self:SetOverflowAmount(self:GetScrollAmount() - math.Clamp(self:GetScrollAmount(), 0, self:GetDomain())) 138 | 139 | local real = self:GetiRealScrollAmount() 140 | self:SetiRealScrollAmount(Lerp(FrameTime() * 10, real, self:GetScrollAmount())) 141 | 142 | local pos = (self:GetiRealScrollAmount() / self:GetDomain()) * (self:GetHorizontal() and (self:GetWide() - self.Grip:GetWide()) or (self:GetTall() - self.Grip:GetTall())) 143 | 144 | if self:GetHorizontal() then 145 | self.Grip:SetX(pos) 146 | else 147 | self.Grip:SetY(pos) 148 | end 149 | 150 | if self:GetHorizontal() then 151 | self:GetCanvas():SetX(-(self:GetiRealScrollAmount())) 152 | else 153 | self:GetCanvas():SetY(-(self:GetiRealScrollAmount())) 154 | end 155 | end 156 | 157 | function PANEL:Think() 158 | self:ScrollThink() 159 | end 160 | 161 | function PANEL:OnMouseWheeled(d) 162 | self:AddScroll(d) 163 | end 164 | 165 | ---- 166 | ---@method 167 | ---@name melon.elements.ScrollBar:AddScroll 168 | ---- 169 | ---@arg (delta: number) Scroll delta to add 170 | ---@arg (pixels: number?) Optional, how many pixels to scroll instead of delta 171 | ---- 172 | ---- Scrolls the scrollbar however much is specified 173 | ---- 174 | function PANEL:AddScroll(delta, amt_in_pixels) 175 | if not self:GetCanvas() then return end 176 | if self:GetDomain() == 0 then return end 177 | 178 | self:SetScrollAmount( 179 | math.Clamp(self:GetScrollAmount() + (amt_in_pixels or self:GetParent():GetScrollPerDelta() * (-delta)), -self:GetParent():GetScrollbarMaxOverflow(), self:GetDomain() + self:GetParent():GetScrollbarMaxOverflow()) 180 | ) 181 | end 182 | 183 | ---- 184 | ---@method 185 | ---@name melon.elements.ScrollBar:ScrollTo 186 | ---- 187 | ---@arg (pos: number) Position to scroll to 188 | ---- 189 | ---- Scrolls to the given coordinate, if horizontal than X, if vertical than Y 190 | ---- 191 | function PANEL:ScrollTo(coord) 192 | if not self:GetCanvas() then return end 193 | if self:GetDomain() == 0 then return end 194 | 195 | self:SetScrollAmount(math.Clamp(coord, 0, self:GetHorizontal() and self:GetCanvas():GetWide() or self:GetCanvas():GetTall())) 196 | end 197 | 198 | ---- 199 | ---@method 200 | ---@name melon.elements.ScrollBar:ScrollToChild 201 | ---- 202 | ---@arg (pnl: Panel) Panel to scroll to 203 | ---- 204 | ---- Scrolls to a child panel, if the panel isnt a child then silently fails 205 | ---- 206 | function PANEL:ScrollToChild(panel) 207 | if not self:GetCanvas() then return end 208 | if self:GetDomain() == 0 then return end 209 | 210 | if not self:GetCanvas():IsOurChild(panel) then return end 211 | 212 | self:SetScrollAmount(math.Clamp(self:GetHorizontal() and panel:GetX() or panel:GetY(), 0, self:GetHorizontal() and self:GetCanvas():GetWide() or self:GetCanvas():GetTall())) 213 | end 214 | 215 | ---- 216 | ---@internal 217 | ---@method 218 | ---@name melon.elements.ScrollBar:Grab 219 | ---- 220 | ---- Initiates grip grabbing 221 | ---- 222 | function PANEL:Grab() 223 | local x, y = self.Grip:LocalCursorPos() 224 | self:SetiGrabbed({ 225 | x = x, 226 | y = y, 227 | xpos = self.Grip:GetX(), 228 | ypos = self.Grip:GetY(), 229 | }) 230 | end 231 | 232 | ---- 233 | ---@method 234 | ---@name melon.elements.ScrollBar:GetDomain 235 | ---- 236 | ---@return (num: number) The domain 237 | ---- 238 | ---- Returns the "Domain", the actual scrollable area of the canvas, so (canvas_size - parent_size) 239 | ---- 240 | function PANEL:GetDomain() 241 | if not IsValid(self:GetCanvas()) then return end 242 | 243 | if self:GetHorizontal() then 244 | return math.max(self:GetCanvas():GetWide() - self:GetParent():GetWide(), 0) 245 | end 246 | 247 | return math.max(self:GetCanvas():GetTall() - self:GetParent():GetTall(), 0) 248 | end 249 | 250 | melon.DebugNamed("ScrollPanel") -------------------------------------------------------------------------------- /lua/melon/core/elements/scroller/cl_scrollpanel.lua: -------------------------------------------------------------------------------- 1 | 2 | melon.elements = melon.elements or {} 3 | 4 | ---- 5 | ---@panel Melon:ScrollPanel 6 | ---@name melon.elements.ScrollPanel 7 | ---- 8 | ---@accessor (ScrollPerDelta: number) How many pixels to scroll per-scroll wheel delta, unscaled 9 | ---@accessor (ScrollbarSize: number) How wide should the scrollbars be? 10 | ---@accessor (ScrollbarPad: number) How much padding should the scrollbars have 11 | ---@accessor (ScrollbarMaxOverflow: number) How much overflow in pixels should we allocate to the scrollbars, unscaled 12 | ---@accessor (PanningEnabled: boolean) Should we allow panning on the scrollbars with middle mouse button? note you need to call :Pan() yourself 13 | ---@accessor (Canvas: Panel) Canvas panel 14 | ---@accessor (VerticalScrollEnabled: boolean) Should we allow vertical scrolling? 15 | ---@accessor (HorizontalScrollEnabled: boolean) Should we allow horizontal scrolling? 16 | ---- 17 | ---- A general purpose unstyled scrollpanel rewrite containing over-content scrollbars, smooth scrolling and canvas based scrolling. 18 | ---- 19 | local PANEL = vgui.Register("Melon:ScrollPanel", {}, "Panel") 20 | AccessorFunc(PANEL, "ScrollPerDelta", "ScrollPerDelta") 21 | AccessorFunc(PANEL, "ScrollbarSize", "ScrollbarSize") 22 | AccessorFunc(PANEL, "ScrollbarPad", "ScrollbarPad") 23 | AccessorFunc(PANEL, "ScrollbarMaxOverflow", "ScrollbarMaxOverflow") 24 | AccessorFunc(PANEL, "PanningEnabled", "PanningEnabled") 25 | AccessorFunc(PANEL, "Canvas", "Canvas") 26 | AccessorFunc(PANEL, "VerticalScrollEnabled", "VerticalScrollEnabled") 27 | AccessorFunc(PANEL, "HorizontalScrollEnabled", "HorizontalScrollEnabled") 28 | 29 | melon.elements.ScrollPanel = PANEL 30 | 31 | function PANEL:SetCanvas(cvs) 32 | self.Canvas = cvs 33 | 34 | cvs:SetParent(self) 35 | self:InvalidateLayout(true) 36 | 37 | self.VScroll:SetCanvas(cvs) 38 | self.HScroll:SetCanvas(cvs) 39 | 40 | self.VScroll:SetZPos(2) 41 | self.HScroll:SetZPos(2) 42 | end 43 | 44 | function PANEL:SetVerticalScrollEnabled(en) 45 | self.VerticalScrollEnabled = en 46 | 47 | self.VScroll:SetVisible(en) 48 | self.VScroll:SetMouseInputEnabled(en) 49 | end 50 | 51 | function PANEL:SetHorizontalScrollEnabled(en) 52 | self.HorizontalScrollEnabled = en 53 | 54 | self.HScroll:SetVisible(en) 55 | self.HScroll:SetMouseInputEnabled(en) 56 | end 57 | 58 | function PANEL:Init() 59 | self.VScroll = vgui.Create("Melon:ScrollBar", self) 60 | self.HScroll = vgui.Create("Melon:ScrollBar", self) 61 | self.HScroll:SetHorizontal(true) 62 | 63 | self:SetScrollPerDelta(127) 64 | self:SetScrollbarSize(8) 65 | self:SetScrollbarPad(4) 66 | self:SetScrollbarMaxOverflow(250) 67 | self:SetPanningEnabled(false) 68 | 69 | self:SetVerticalScrollEnabled(true) 70 | self:SetHorizontalScrollEnabled(true) 71 | end 72 | 73 | function PANEL:ScrollPerf(w, h) 74 | local size = melon.Scale(self:GetScrollbarSize()) 75 | local pad = melon.Scale(self:GetScrollbarPad()) 76 | 77 | self.VScroll:SetSize(size, h - (pad * 2)) 78 | self.VScroll:SetPos(w - size - pad, pad) 79 | 80 | self.HScroll:SetSize(w - ((self:GetVerticalScrollEnabled() and self.VScroll:GetDomain() != 0) and (size + (pad * 3)) or (pad * 2)), size) 81 | self.HScroll:SetPos(pad, h - size - pad) 82 | end 83 | 84 | function PANEL:PerformLayout(w, h) 85 | self:ScrollPerf(w, h) 86 | end 87 | 88 | function PANEL:OnMouseWheeled(d) 89 | if ( 90 | (self.VScroll:IsHovered() and self.VScroll:GetDomain() != 0) or (self.HScroll:IsHovered() and self.HScroll:GetDomain() != 0) 91 | ) or ( 92 | self.VScroll:IsChildHovered() or self.HScroll:IsChildHovered() 93 | ) then return end 94 | 95 | if self:GetHorizontalScrollEnabled() and (input.IsShiftDown() or self.VScroll:GetDomain() == 0) then 96 | return self.HScroll:OnMouseWheeled(d) 97 | end 98 | 99 | self.VScroll:OnMouseWheeled(d) 100 | end 101 | 102 | function PANEL:OnMousePressed(m) 103 | if m == MOUSE_MIDDLE then 104 | self:Pan() 105 | end 106 | end 107 | 108 | ---- 109 | ---@method 110 | ---@name melon.elements.ScrollPanel:Pan 111 | ---- 112 | ---- Start panning, should only be called while MOUSE_MIDDLE is down 113 | ---- Automatically abides by the PanningEnabled accessor 114 | ---- 115 | function PANEL:Pan() 116 | if not self:GetPanningEnabled() then 117 | return 118 | end 119 | 120 | self.VScroll:SetiPanning(true) 121 | self.VScroll:Grab() 122 | 123 | if self:GetHorizontalScrollEnabled() then 124 | self.HScroll:SetiPanning(true) 125 | self.HScroll:Grab() 126 | end 127 | end 128 | 129 | melon.DebugNamed("ScrollPanel", function() 130 | melon.DebugPanel(PANEL, function(p) 131 | local canvas = vgui.Create("Panel", p) 132 | 133 | p:SetCanvas(canvas) 134 | canvas:SetSize(p:GetWide() * 1, p:GetTall() * 20) 135 | 136 | for i = 0, 40 do 137 | local b = vgui.Create("Panel", canvas) 138 | b:Dock(TOP) 139 | -- b:SetText(i) 140 | end 141 | end ) 142 | end ) -------------------------------------------------------------------------------- /lua/melon/core/extensions/cl_panel.lua: -------------------------------------------------------------------------------- 1 | 2 | local meta = FindMetaTable("Panel") 3 | 4 | ---- 5 | ---@method 6 | ---@name Panel.GetClippingBounds 7 | ---- 8 | ---@return (minx: number) Minimum X coord of the panels clipping bounds 9 | ---@return (miny: number) Minimum Y coord of the panels clipping bounds 10 | ---@return (maxx: number) Maximum X coord of the panels clipping bounds 11 | ---@return (maxy: number) Maximum Y coord of the panels clipping bounds 12 | ---- 13 | ---- Gets the clipping bounds of this panel, for use with render.SetScissorRect 14 | ---- This goes up the tree to find how much of the panel is *actually* visible 15 | ---- 16 | function meta:GetClippingBounds(done) 17 | done = done or {} 18 | if done[self] then 19 | return 20 | end 21 | done[self] = true 22 | 23 | local minx, miny = self:LocalToScreen(0, 0) 24 | if minx == 0 and miny == 0 and self:GetWide() == ScrW() and self:GetTall() == ScrH() then 25 | return 26 | end 27 | 28 | local maxx, maxy = minx + self:GetWide(), miny + self:GetTall() 29 | 30 | local parent = self:GetParent() 31 | if not IsValid(parent) then 32 | return minx, miny, maxx, maxy 33 | end 34 | 35 | local px, py, pxx, pyy = parent:GetClippingBounds(done) 36 | 37 | if not px then return minx, miny, maxx, maxy end 38 | 39 | minx = math.max(px, minx) 40 | miny = math.max(py, miny) 41 | maxx = math.min(maxx, pxx) 42 | maxy = math.min(maxy, pyy) 43 | 44 | return minx, miny, maxx, maxy 45 | end 46 | 47 | melon.DebugPanel("DPanel", function(p) 48 | local q = vgui.Create("DPanel", p) 49 | q:SetPos(-p:GetWide() / 2, p:GetTall() / 2) 50 | q:SetSize(p:GetSize()) 51 | 52 | local r = vgui.Create("DPanel", q) 53 | r:SetSize(p:GetWide(), melon.Scale(20)) 54 | r:SetPos(p:GetWide() - 10, melon.Scale(40)) 55 | 56 | p:SetBackgroundColor(Color(22, 22, 22)) 57 | q:SetBackgroundColor(Color(255, 255, 255, 10)) 58 | r:SetBackgroundColor(q:GetBackgroundColor()) 59 | 60 | function p:PaintOver() 61 | local oc = DisableClipping(true) 62 | 63 | local rx, ry = p:LocalToScreen(0, 0) 64 | local x, y, xx, yy = r:GetClippingBounds() 65 | 66 | render.SetScissorRect(x, y, xx, yy, true) 67 | surface.SetDrawColor(melon.colors.Rainbow()) 68 | surface.DrawRect(-rx, -ry, ScrW(), ScrH()) 69 | render.SetScissorRect(0, 0, 0, 0, false) 70 | 71 | surface.SetDrawColor(255, 255, 255) 72 | surface.DrawOutlinedRect(q:GetX(), q:GetY(), q:GetWide(), q:GetTall()) 73 | 74 | surface.DrawOutlinedRect(q:GetX() + r:GetX(), q:GetY() + r:GetY(), r:GetWide(), r:GetTall()) 75 | 76 | DisableClipping(oc) 77 | end 78 | end ) -------------------------------------------------------------------------------- /lua/melon/core/extensions/cl_panel_extensions.lua: -------------------------------------------------------------------------------- 1 | 2 | local applied = {} 3 | 4 | ---- 5 | ---@module 6 | ---@name melon.Extensions 7 | ---@deprecated 8 | ---@alias asdasd 9 | ---@realm SHARED 10 | ---- 11 | ---- Extends an object at runtime instead of directly altering its metatable. 12 | ---- Done this way to potentially avoid conflicts with other systems? 13 | ---- 14 | melon.Extensions = melon.Extensions or {} 15 | 16 | ---- 17 | ---@deprecated 18 | ---@member 19 | ---@name melon.Extensions.PANEL 20 | ---@realm CLIENT 21 | ---- 22 | ---- All extensions to Panels 23 | ---- 24 | melon.Extensions.PANEL = melon.Extensions.PANEL or {} 25 | 26 | ---- 27 | ---@name melon.Extensions.RegisterPanelExtension 28 | ---- 29 | ---@arg (name: string) Name of the extension to register 30 | ---@arg (panel: table) Functions to add to the Panel 31 | ---- 32 | ---- Registers an extension for Panels 33 | ---- 34 | function melon.Extensions.RegisterPanelExtension(name, PANEL) 35 | melon.Extensions.PANEL[name] = PANEL 36 | 37 | melon.Debug(melon.Extensions.RefreshExtensions) 38 | end 39 | 40 | ---- 41 | ---@name melon.Extensions.ApplyPanelExtension 42 | ---- 43 | ---@arg (panel: panel) Panel to apply the extension to 44 | ---@arg (name: string) Name of the extension to apply 45 | ---- 46 | ---- Applies a Panel Extension to a Panel 47 | ---- 48 | function melon.Extensions.ApplyPanelExtension(Panel, name) 49 | local ext = melon.Extensions.PANEL[name] 50 | if not ext then return end 51 | 52 | Panel.extensions = Panel.extensions or {} 53 | Panel.extensions[name] = true 54 | 55 | if ext.OnExtensionAdded then 56 | ext.OnExtensionAdded(Panel) 57 | end 58 | 59 | table.Merge(Panel, (isfunction(ext) and ext()) or ext) 60 | 61 | Panel.OnExtensionAdded = nil 62 | end 63 | 64 | ---- 65 | ---@internal 66 | ---@name melon.Extensions.RefreshExtensions 67 | ---- 68 | ---- Refreshes all extensions on all existing Panels globally, reapplying them. 69 | ---- 70 | function melon.Extensions.RefreshExtensions() 71 | for k, v in pairs(applied) do 72 | if IsValid(v) then 73 | v:RefreshExtensions() 74 | end 75 | end 76 | end 77 | 78 | ---- 79 | ---@todo 80 | ---- 81 | local meta = FindMetaTable("Panel") 82 | function meta:ApplyExtension(name) 83 | melon.Extensions.ApplyPanelExtension(self, name) 84 | end 85 | 86 | function meta:RefreshExtensions() 87 | for k, _ in pairs(self.extensions) do 88 | self:ApplyExtension(k) 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lua/melon/core/extensions/panel/cl_pext_hook.lua: -------------------------------------------------------------------------------- 1 | 2 | --[[ 3 | Hook extension for Panel objects 4 | this allows panels to directly interact with hooks without some weird intermediate 5 | and without cluttering up hookspace 6 | ]] 7 | 8 | melon.Extensions.PANEL.HOOKWatching = melon.Extensions.PANEL.HOOKWatching or {} 9 | local w = melon.Extensions.PANEL.HOOKWatching 10 | 11 | local PANEL = {} 12 | 13 | function PANEL:WatchHook(name) 14 | if not w[name] then 15 | hook.Add(name, "MelonLib:PanelWatch", function(...) 16 | for pnl, valid in w[name] do 17 | if IsValid(pnl) and valid then 18 | pnl["Hook_" .. name](pnl, ...) 19 | else 20 | w[name][pnl] = nil 21 | end 22 | end 23 | end ) 24 | end 25 | 26 | w[name][self] = true 27 | end 28 | 29 | function PANEL:UnwatchHook(name) 30 | w[name] = w[name] or {} 31 | w[name][self] = nil 32 | 33 | if table.IsEmpty(w[name]) then 34 | w[name] = nil 35 | hook.Remove(name, "MelonLib:PanelWatch") 36 | end 37 | end 38 | 39 | melon.Extensions.RegisterPanelExtension("hook", PANEL) -------------------------------------------------------------------------------- /lua/melon/core/extensions/panel/cl_pext_net.lua: -------------------------------------------------------------------------------- 1 | 2 | --[[ 3 | Net extension for Panel objects 4 | Allows panels to interact with net messages directly instead of jank workarounds 5 | ]] 6 | 7 | local PANEL = {} 8 | 9 | function PANEL:OnExtensionAdded() 10 | self.NetWatching = {} 11 | end 12 | 13 | function PANEL:WatchNet(msg) 14 | self.NetWatching[msg] = true 15 | 16 | melon.net.Watch(msg, self, function(len, ply) 17 | if IsValid(self) and self.NetWatching[msg] then 18 | self["Net_" .. msg](self, len, ply) 19 | else 20 | melon.net.Unwatch(msg, self) 21 | end 22 | end ) 23 | end 24 | 25 | function PANEL:UnwatchNet(msg) 26 | self.NetWatching[msg] = false 27 | 28 | melon.net.Unwatch(msg, self) 29 | end 30 | 31 | melon.Extensions.RegisterPanelExtension("net", PANEL) -------------------------------------------------------------------------------- /lua/melon/core/misc/format/sh_filters.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Filters for the formatting system 3 | 4 | melon.string = melon.string or {} 5 | melon.string.filters = melon.string.filters or {} 6 | local f = melon.string.filters 7 | 8 | local function err(msg) 9 | return "" 10 | end 11 | 12 | -- String Ops 13 | function f.upper(str) 14 | return string.upper(str) 15 | end 16 | 17 | function f.capitalize(str) 18 | return str:sub(1, 1):upper() .. str:sub(2, -1) 19 | end 20 | 21 | -- Num Ops 22 | function f.isplural(num) 23 | num = tonumber(num) 24 | 25 | if not num then 26 | return err("Invalid Number passed to isplural ('" .. num .. "')") 27 | end 28 | 29 | return num == 1 and "" or "s" 30 | end 31 | 32 | function f.round(num, places) 33 | local t = tonumber(num) 34 | 35 | if not t then return err("Attempted to round non-number '" .. tostring(num) .. "'") end 36 | 37 | return math.Round(num, places) 38 | end 39 | 40 | function f.comma(num) 41 | return string.Comma(num) 42 | end 43 | 44 | -- Func Ops 45 | function f.call(func, ...) 46 | if not isfunction(func) then 47 | return err("Attempted to call non-function '" .. tostring(func) .. "'") 48 | end 49 | 50 | return func(...) 51 | end 52 | 53 | -- Type Ops 54 | function f.int(str) 55 | return math.Round(tonumber(str), 0) or err("Invalid Number '" .. str .. "'") 56 | end 57 | 58 | function f.str(val) 59 | return tostring(val) 60 | end 61 | 62 | function f.prettycolor(col) 63 | if not melon.IsColor(col) then 64 | return err("Invalid Color '" .. tostring(col) .. "'") 65 | end 66 | 67 | return "col(" .. col.r .. ", " .. col.g .. ", " .. col.b .. ")" 68 | end 69 | 70 | function f.realcolor(col) 71 | if not melon.IsColor(col) then 72 | return err("Invalid Color '" .. tostring(col) .. "'") 73 | end 74 | 75 | return "Color(" .. col.r .. ", " .. col.g .. ", " .. col.b .. ", " .. (col.a or 255) .. ")" 76 | end 77 | 78 | -- Money ops 79 | function f.money(val) 80 | if not isnumber(val) then 81 | return err("Invalid number '" .. tostring(val) .. "'") 82 | end 83 | 84 | if not DarkRP then 85 | return err("Cannot format money outside of DarkRP") 86 | end 87 | 88 | return DarkRP.formatMoney(val) 89 | end -------------------------------------------------------------------------------- /lua/melon/core/misc/format/sh_stringformat.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@module 4 | ---@name melon.string 5 | ---@realm SHARED 6 | ---- 7 | ---- String manipulation library, think a templating engine 8 | ---- Syntax is simple 9 | ---- 10 | ---` 11 | ---` melon.string.Format("{1} {2} {3}", "first", "second", "third") 12 | ---` melon.string.Format("{1.1}", {"value in a table"}) 13 | ---` melon.string.Format("{1.key}", {key = "by a key"}) 14 | ---` melon.string.Format("{1.key | uppercase}", {key = "by a key"}) 15 | ---` melon.string.Format("{uppercase($1)}", "make this uppercase") 16 | ---` melon.string.Format("the following is escaped: {!uppercase($1)}") 17 | ---` 18 | melon.string = melon.string or {} 19 | 20 | ---- 21 | ---@member 22 | ---@name melon.string.filters 23 | ---- 24 | ---- List of all string filters 25 | ---- 26 | melon.string.filters = melon.string.filters or {} 27 | 28 | ---- 29 | ---@internal 30 | ---@name melon.string.Qualify 31 | ---- 32 | function melon.string.Qualify(tbl, to, notstr) 33 | to = string.gsub(to, "%s", "") 34 | local keys = string.Explode("[%.%:]", to, true) 35 | local val = tbl 36 | 37 | for k,v in ipairs(keys) do 38 | if not istable(val) and not IsEntity(val) then 39 | return "indexing non-table (" .. tostring(val) .. ")" 40 | end 41 | 42 | val = val[tonumber(v) or tostring(v)] 43 | end 44 | 45 | return ((val == tbl) and "") or (notstr and val) or (tostring(val)) 46 | end 47 | 48 | ---- 49 | ---@internal 50 | ---@name melon.string.QualifyFil 51 | ---- 52 | ---- Extremely unsafe for user input 53 | ---- 54 | function melon.string.QualifyFil(tbl, to, current) 55 | local t = string.Split(to, "|") 56 | local val 57 | 58 | for k,v in ipairs(t) do 59 | v = string.Trim(v) 60 | 61 | local call = v:match("%((.-)%)$") 62 | if call then -- Explicit Filter Call 63 | local args = melon.string.ParseArgs(call, tbl) 64 | val = melon.string.CallFil(v:sub(1, -#call - 3), val or table.remove(args, 1), args) 65 | else 66 | val = melon.string.Qualify(tbl, current or v, true) 67 | end 68 | end 69 | 70 | return val 71 | end 72 | 73 | ---- 74 | ---@internal 75 | ---@name melon.string.CallFil 76 | ---- 77 | function melon.string.CallFil(fil, arg, args) 78 | if arg == nil then 79 | return "" 80 | end 81 | 82 | if melon.string.filters[fil] then 83 | return melon.string.filters[fil](arg, unpack(args)) 84 | end 85 | return "" 86 | end 87 | 88 | ---- 89 | ---@name melon.string.Format 90 | ---- 91 | ---@arg (fmt: string) String format to use, see [melon.string] for a reference 92 | ---@arg (args: ...any) Any values to be passed to the formatter 93 | ---@return (str: string) Formatted string 94 | ---- 95 | ---- Formats a string using the melonlib formatter 96 | ---- 97 | function melon.string.Format(fmt, ...) 98 | local varargs = {...} 99 | if (#varargs == 1) and istable(varargs[1]) then 100 | varargs = varargs[1] 101 | end 102 | 103 | local current = 0 104 | return string.gsub(fmt, "{(.-)}", function(mtch, ...) 105 | if mtch[1] == "!" then 106 | return "{" .. mtch:sub(2, -1) .. "}" 107 | end 108 | 109 | if mtch == "" then 110 | current = current + 1 111 | return tostring(melon.string.QualifyFil(varargs, mtch, current)) 112 | end 113 | 114 | return tostring(melon.string.QualifyFil(varargs, mtch)) 115 | end) 116 | end 117 | 118 | ---- 119 | ---@internal 120 | ---@name melon.string.ParseArgs 121 | ---- 122 | function melon.string.ParseArgs(str, tbl) 123 | local args = string.Split(str, ",") 124 | local toret = {} 125 | 126 | for k,v in pairs(args) do 127 | v = v:Trim() 128 | if isnumber(tonumber(v)) then 129 | toret[k] = tonumber(v) 130 | elseif v[1] == "$" then 131 | toret[k] = melon.string.QualifyFil(tbl, v:sub(2, -1)) 132 | else 133 | toret[k] = v 134 | end 135 | end 136 | 137 | return toret 138 | end 139 | 140 | ---- 141 | ---@name melon.string.print 142 | ---- 143 | ---@arg (fmt: string) String to be formatted, see [melon.string] for reference 144 | ---@arg (args: ...any) Arguments to be passed to the formatter 145 | ---- 146 | ---- Formats and prints the given format, quick function 147 | ---- 148 | function melon.string.print(fmt, ...) 149 | print(({melon.string.Format(fmt, ...)})[1]) 150 | end -------------------------------------------------------------------------------- /lua/melon/core/misc/lua/builder/sh_expr.lua: -------------------------------------------------------------------------------- 1 | 2 | melon.lua = melon.lua or {} 3 | 4 | local EXPR = {} 5 | EXPR.__index = EXPR 6 | 7 | do --- Constructors 8 | function melon.lua.Nil(meta) 9 | return setmetatable({}, EXPR):Init(melon.lua.NodeKinds.Nil, false, meta) 10 | end 11 | 12 | function melon.lua.False(meta) 13 | return setmetatable({}, EXPR):Init(melon.lua.NodeKinds.False, false, meta) 14 | end 15 | 16 | function melon.lua.True(meta) 17 | return setmetatable({}, EXPR):Init(melon.lua.NodeKinds.True, false, meta) 18 | end 19 | 20 | function melon.lua.Variadic(meta) 21 | return setmetatable({}, EXPR):Init(melon.lua.NodeKinds.Variadic, false, meta) 22 | end 23 | 24 | function melon.lua.Identifier(name, meta) 25 | if melon.Assert(isstring(name), "Identifier name should be a string") then return end 26 | 27 | return setmetatable({}, EXPR):Init(melon.lua.NodeKinds.Identifier, name, token) 28 | end 29 | 30 | function melon.lua.Number(number, meta) 31 | if melon.Assert(isstring(number) or isnumber(number), "Number should should be convertable into a string") then return end 32 | 33 | return setmetatable({}, EXPR):Init(melon.lua.NodeKinds.Number, tostring(number), meta) 34 | end 35 | 36 | function melon.lua.String(string, opening, closing, meta) 37 | if melon.Assert(isstring(string), "String contents should be a string") then return end 38 | if melon.Assert(isstring(string), "Provide a valid string opener") then return end 39 | if melon.Assert(isstring(string), "Provide a valid string closer") then return end 40 | 41 | return setmetatable({}, EXPR):Init(melon.lua.NodeKinds.String, { 42 | text = string, 43 | opening = opening, 44 | closing = closing, 45 | }, meta) 46 | end 47 | 48 | function melon.lua.Table(tokens) 49 | return setmetatable({}, EXPR):Init(melon.lua.NodeKinds.Table, { 50 | hashfields = {}, 51 | arrayfields = {}, 52 | }, tokens) 53 | end 54 | 55 | function melon.lua.Lambda(params, chunk, tokens) 56 | if melon.Assert(istable(params), "Lambda parameters should be a table of nodes") then return end 57 | if melon.Assert(melon.lua.IsChunk(chunk), "Chunk should be a valid melon.CHUNK") then return end 58 | 59 | for k, v in pairs(params) do 60 | if melon.Assert(not isstring(v), "Lambda parameters should be a table of Identifier's or Variadics") then 61 | return 62 | end 63 | end 64 | 65 | return setmetatable({}, EXPR):Init(melon.lua.NodeKinds.Lambda, { 66 | params = params, 67 | chunk = chunk 68 | }, tokens) 69 | end 70 | end 71 | 72 | function EXPR:Init(kind, data, metadata) 73 | self.kind = kind 74 | self.data = data 75 | self.metadata = metadata or {} 76 | 77 | return self 78 | end 79 | 80 | function EXPR:DotIndex(name, metadata) 81 | if melon.Assert(melon.lua.LeftSideExpressions[self.kind] and true, "You can only index allowed left-hand-side expressions") then return end 82 | 83 | local lhs = { 84 | kind = self.kind, 85 | data = self.data, 86 | metadata = self.metadata, 87 | } 88 | 89 | self.kind = melon.lua.NodeKinds.DotIndex 90 | self.data = { 91 | lhs = lhs, 92 | name = name, 93 | } 94 | self.metadata = metadata 95 | 96 | return self 97 | end 98 | 99 | function EXPR:BracketIndex(expr, metadata) 100 | if melon.Assert(melon.lua.LeftSideExpressions[self.kind] and true, "You can only index allowed left-hand-side expressions") then return end 101 | 102 | local lhs = { 103 | kind = self.kind, 104 | data = self.data, 105 | metadata = self.metadata, 106 | } 107 | 108 | self.kind = melon.lua.NodeKinds.BracketIndex 109 | self.data = { 110 | lhs = lhs, 111 | expr = expr, 112 | } 113 | self.metadata = metadata 114 | 115 | return self 116 | end 117 | 118 | function EXPR:Parenthesize(metadata) 119 | local expr = { 120 | kind = self.kind, 121 | data = self.data, 122 | metadata = self.metadata, 123 | } 124 | 125 | self.kind = melon.lua.NodeKinds.Parenthesized 126 | self.data = expr 127 | self.metadata = metadata 128 | 129 | return self 130 | end 131 | 132 | function EXPR:Call(args, metadata) 133 | if melon.Assert(melon.lua.LeftSideExpressions[self.kind] and true, "You can only call allowed left-hand-side expressions") then return end 134 | 135 | local calling = { 136 | kind = self.kind, 137 | data = self.data, 138 | metadata = self.metadata, 139 | } 140 | 141 | self.kind = melon.lua.NodeKinds.FunctionCall 142 | self.data = { 143 | calling = calling, 144 | args = args or {}, 145 | } 146 | self.metadata = metadata 147 | 148 | return self 149 | end 150 | 151 | function EXPR:MethodCall(name, args, metadata) 152 | if melon.Assert(melon.lua.LeftSideExpressions[self.kind] and true, "You can only call allowed left-hand-side expressions") then return end 153 | 154 | local calling = { 155 | kind = self.kind, 156 | data = self.data, 157 | metadata = self.metadata, 158 | } 159 | 160 | self.kind = melon.lua.NodeKinds.MethodCall 161 | self.data = { 162 | name = name, 163 | calling = calling, 164 | args = args, 165 | } 166 | self.metadata = metadata 167 | 168 | return self 169 | end 170 | 171 | function EXPR:BinaryOp(op, rhs, metadata) 172 | local lhs = { 173 | kind = self.kind, 174 | data = self.data, 175 | metadata = self.metadata, 176 | } 177 | 178 | self.kind = melon.lua.NodeKinds.BinaryOp 179 | self.data = { 180 | lhs = lhs, 181 | op = op, 182 | rhs = rhs, 183 | } 184 | self.metadata = metadata 185 | 186 | return self 187 | end 188 | 189 | function EXPR:UnaryOp(op, metadata) 190 | local expr = { 191 | kind = self.kind, 192 | data = self.data, 193 | metadata = self.metadata, 194 | } 195 | 196 | self.kind = melon.lua.NodeKinds.UnaryOp 197 | self.data = { 198 | op = op, 199 | expr = expr, 200 | } 201 | self.metadata = metadata 202 | 203 | return self 204 | end 205 | 206 | function EXPR:TableHashField(keyexpr, valexpr) 207 | if melon.Assert(melon.lua.ExpressionKinds[keyexpr.kind] and true, "Table HashField Key expression must be an expression") then return end 208 | if melon.Assert(melon.lua.ExpressionKinds[valexpr.kind] and true, "Table HashField Value expression must be an expression") then return end 209 | 210 | table.insert(self.hashfields, {keyexpr, valexpr}) 211 | return self 212 | end 213 | 214 | function EXPR:TableArrayField(expr) 215 | if melon.Assert(melon.lua.ExpressionKinds[expr.kind] and true, "Table ArrayField expression must be an expression") then return end 216 | table.insert(self.arrayfields, expr) 217 | return self 218 | end -------------------------------------------------------------------------------- /lua/melon/core/misc/lua/builder/sh_kinds.lua: -------------------------------------------------------------------------------- 1 | 2 | melon.lua = melon.lua or {} 3 | 4 | ---- 5 | ---@member 6 | ---@name melon.lua.NodeKinds 7 | ---- 8 | ---- Contains all kinds of AST nodes, includes statements and expressions 9 | ---- 10 | melon.lua.NodeKinds = {} 11 | 12 | -- Statements 13 | melon.lua.NodeKinds.GlobalAssign = "GlobalAssign" 14 | melon.lua.NodeKinds.LocalAssign = "LocalAssign" 15 | melon.lua.NodeKinds.FunctionCall = "FunctionCall" 16 | melon.lua.NodeKinds.DoBlock = "DoBlock" 17 | melon.lua.NodeKinds.WhileBlock = "WhileBlock" 18 | melon.lua.NodeKinds.RepeatUntilBlock = "RepeatUntilBlock" 19 | melon.lua.NodeKinds.IfBlock = "IfBlock" 20 | melon.lua.NodeKinds.ForBlock = "ForBlock" 21 | melon.lua.NodeKinds.ForIBlock = "ForIBlock" 22 | melon.lua.NodeKinds.FunctionDecl = "FunctionDecl" 23 | melon.lua.NodeKinds.LocalFunctionDecl = "LocalFunctionDecl" 24 | melon.lua.NodeKinds.Return = "Return" 25 | melon.lua.NodeKinds.Break = "Break" 26 | melon.lua.NodeKinds.Goto = "Goto" 27 | melon.lua.NodeKinds.Label = "Label" 28 | 29 | -- Expressions 30 | melon.lua.NodeKinds.Nil = "Nil" 31 | melon.lua.NodeKinds.False = "False" 32 | melon.lua.NodeKinds.True = "True" 33 | melon.lua.NodeKinds.Variadic = "Variadic" 34 | melon.lua.NodeKinds.Number = "Number" 35 | melon.lua.NodeKinds.String = "String" 36 | melon.lua.NodeKinds.Identifier = "Identifier" 37 | melon.lua.NodeKinds.Lambda = "Lambda" 38 | melon.lua.NodeKinds.DotIndex = "DotIndex" 39 | melon.lua.NodeKinds.BracketIndex = "BracketIndex" 40 | melon.lua.NodeKinds.Parenthesized = "Parenthesized" 41 | melon.lua.NodeKinds.FunctionCall = "FunctionCall" 42 | melon.lua.NodeKinds.MethodCall = "MethodCall" 43 | melon.lua.NodeKinds.Table = "Table" 44 | melon.lua.NodeKinds.BinaryOp = "BinaryOp" 45 | melon.lua.NodeKinds.UnaryOp = "UnaryOp" 46 | 47 | melon.lua.StatementKinds = { 48 | [melon.lua.NodeKinds.GlobalAssign] = melon.lua.NodeKinds.GlobalAssign, 49 | [melon.lua.NodeKinds.LocalAssign] = melon.lua.NodeKinds.LocalAssign, 50 | [melon.lua.NodeKinds.FunctionCall] = melon.lua.NodeKinds.FunctionCall, 51 | [melon.lua.NodeKinds.DoBlock] = melon.lua.NodeKinds.DoBlock, 52 | [melon.lua.NodeKinds.WhileBlock] = melon.lua.NodeKinds.WhileBlock, 53 | [melon.lua.NodeKinds.RepeatUntilBlock] = melon.lua.NodeKinds.RepeatUntilBlock, 54 | [melon.lua.NodeKinds.IfBlock] = melon.lua.NodeKinds.IfBlock, 55 | [melon.lua.NodeKinds.ForBlock] = melon.lua.NodeKinds.ForBlock, 56 | [melon.lua.NodeKinds.ForIBlock] = melon.lua.NodeKinds.ForIBlock, 57 | [melon.lua.NodeKinds.FunctionDecl] = melon.lua.NodeKinds.FunctionDecl, 58 | [melon.lua.NodeKinds.LocalFunctionDecl] = melon.lua.NodeKinds.LocalFunctionDecl, 59 | [melon.lua.NodeKinds.Return] = melon.lua.NodeKinds.Return, 60 | [melon.lua.NodeKinds.Break] = melon.lua.NodeKinds.Break, 61 | [melon.lua.NodeKinds.Goto] = melon.lua.NodeKinds.Goto, 62 | [melon.lua.NodeKinds.Label] = melon.lua.NodeKinds.Label, 63 | } 64 | 65 | melon.lua.ExpressionKinds = { 66 | [melon.lua.NodeKinds.Nil] = melon.lua.NodeKinds.Nil, 67 | [melon.lua.NodeKinds.False] = melon.lua.NodeKinds.False, 68 | [melon.lua.NodeKinds.True] = melon.lua.NodeKinds.True, 69 | [melon.lua.NodeKinds.Variadic] = melon.lua.NodeKinds.Variadic, 70 | [melon.lua.NodeKinds.Identifier] = melon.lua.NodeKinds.Identifier, 71 | [melon.lua.NodeKinds.Number] = melon.lua.NodeKinds.Number, 72 | [melon.lua.NodeKinds.String] = melon.lua.NodeKinds.String, 73 | [melon.lua.NodeKinds.Lambda] = melon.lua.NodeKinds.Lambda, 74 | [melon.lua.NodeKinds.DotIndex] = melon.lua.NodeKinds.DotIndex, 75 | [melon.lua.NodeKinds.BracketIndex] = melon.lua.NodeKinds.BracketIndex, 76 | [melon.lua.NodeKinds.Parenthesized] = melon.lua.NodeKinds.Parenthesized, 77 | [melon.lua.NodeKinds.FunctionCall] = melon.lua.NodeKinds.FunctionCall, 78 | [melon.lua.NodeKinds.MethodCall] = melon.lua.NodeKinds.MethodCall, 79 | [melon.lua.NodeKinds.Table] = melon.lua.NodeKinds.Table, 80 | [melon.lua.NodeKinds.BinaryOp] = melon.lua.NodeKinds.BinaryOp, 81 | [melon.lua.NodeKinds.UnaryOp] = melon.lua.NodeKinds.UnaryOp, 82 | } 83 | 84 | melon.lua.LeftSideExpressions = { 85 | [melon.lua.NodeKinds.Identifier] = melon.lua.NodeKinds.Identifier, 86 | [melon.lua.NodeKinds.DotIndex] = melon.lua.NodeKinds.DotIndex, 87 | [melon.lua.NodeKinds.BracketIndex] = melon.lua.NodeKinds.BracketIndex, 88 | [melon.lua.NodeKinds.Parenthesized] = melon.lua.NodeKinds.Parenthesized, 89 | [melon.lua.NodeKinds.FunctionCall] = melon.lua.NodeKinds.FunctionCall, 90 | [melon.lua.NodeKinds.MethodCall] = melon.lua.NodeKinds.MethodCall, 91 | } 92 | 93 | melon.lua.BinOpKinds = {} 94 | melon.lua.BinOpKinds.Add = "+" 95 | melon.lua.BinOpKinds.Subtract = "-" 96 | melon.lua.BinOpKinds.Multiply = "*" 97 | melon.lua.BinOpKinds.Divide = "/" 98 | melon.lua.BinOpKinds.Power = "^" 99 | melon.lua.BinOpKinds.Modulo = "%" 100 | 101 | melon.lua.BinOpKinds.GreaterThan = ">" 102 | melon.lua.BinOpKinds.LessThan = "<" 103 | melon.lua.BinOpKinds.GreaterThanEqual = ">=" 104 | melon.lua.BinOpKinds.LessThanEqual = "<=" 105 | 106 | melon.lua.BinOpKinds.Equal = "==" 107 | melon.lua.BinOpKinds.NotEqualGmod = "!=" 108 | melon.lua.BinOpKinds.NotEqual = "~=" 109 | melon.lua.BinOpKinds.And = "and" 110 | melon.lua.BinOpKinds.AndGmod = "&&" 111 | melon.lua.BinOpKinds.Or = "or" 112 | melon.lua.BinOpKinds.OrGmod = "||" 113 | melon.lua.BinOpKinds.Concat = ".." 114 | 115 | melon.lua.UnOpKinds = {} 116 | melon.lua.UnOpKinds.Not = "not" 117 | melon.lua.UnOpKinds.NotGmod = "!" 118 | melon.lua.UnOpKinds.Length = "#" 119 | 120 | -------------------------------------------------------------------------------- /lua/melon/core/misc/sh_grid.lua: -------------------------------------------------------------------------------- 1 | 2 | local G = {} 3 | G.__index = G 4 | 5 | ---- 6 | ---@class 7 | ---@name melon.GridObject 8 | ---- 9 | ---- Grid Object Class 10 | ---- 11 | 12 | ---- 13 | ---@method 14 | ---@name melon.GridObject.SetSize 15 | ---- 16 | ---@arg (w: number) Width to set 17 | ---@arg (h: number) Height to set 18 | ---- 19 | ---- Set the size of the grid 20 | ---- 21 | function G:SetSize(w, h) 22 | self.w = w 23 | self.h = h 24 | end 25 | 26 | ---- 27 | ---@method 28 | ---@name melon.GridObject.GetSize 29 | ---- 30 | ---@return (w: number) Width of the object 31 | ---@return (h: number) Height of the object 32 | ---- 33 | ---- Get the size of the grid 34 | ---- 35 | function G:GetSize() 36 | return self.w, self.h 37 | end 38 | 39 | ---- 40 | ---@method 41 | ---@name melon.GridObject.Set 42 | ---- 43 | ---@arg (x: number) X to set 44 | ---@arg (y: number) Y to set 45 | ---@arg (val: any) Value to set the coord to 46 | ---- 47 | ---- Sets a value at X and Y to the given value 48 | ---- 49 | function G:Set(x, y, val) 50 | if (not self.grid[x]) or (not self.grid[x][y]) then 51 | return false 52 | end 53 | 54 | self.grid[x][y] = val 55 | return true 56 | end 57 | 58 | ---- 59 | ---@method 60 | ---@name melon.GridObject.Get 61 | ---- 62 | ---@arg (x: number) X to get 63 | ---@arg (y: number) Y to get 64 | ---@return (val: any) Value of the coord 65 | ---- 66 | ---- Gets a value at the given coords 67 | ---- 68 | function G:Get(x, y) 69 | if (not self.grid[x]) then 70 | return false 71 | end 72 | 73 | return self.grid[x][y] 74 | end 75 | 76 | ---- 77 | ---@method 78 | ---@name melon.GridObject.Increment 79 | ---- 80 | ---@arg (x: number) X to get 81 | ---@arg (y: number) Y to get 82 | ---- 83 | ---- Increments the value at the given coord 84 | ---- 85 | function G:Increment(x, y) 86 | if not self.grid[x] then 87 | return 88 | end 89 | 90 | if isnumber(self.grid[x][y]) or self.grid[x][y] == nil then 91 | self.grid[x][y] = (self.grid[x][y] or 0) + 1 92 | end 93 | end 94 | 95 | ---- 96 | ---@method 97 | ---@name melon.GridObject.Update 98 | ---- 99 | ---- Updates the grid object 100 | ---- 101 | function G:Update() 102 | self.grid = {} 103 | 104 | for x = 1, self.w do 105 | self.grid[x] = {} 106 | 107 | for y = 1, self.h do 108 | self.grid[x][y] = 0 109 | end 110 | end 111 | end 112 | 113 | ---- 114 | ---@name melon.Grid 115 | ---- 116 | ---@arg (w: number) Width to create the grid as 117 | ---@arg (h: number) Height to create the grid as 118 | ---@return (grid: melon.GridObject) Grid object that was created 119 | ---- 120 | ---- Creates a [melon.GridObject] 121 | ---- 122 | function melon.Grid(w, h) 123 | local x = setmetatable({}, G) 124 | x.w = w 125 | x.h = h 126 | x:Update() 127 | 128 | return x 129 | end -------------------------------------------------------------------------------- /lua/melon/core/misc/sh_math.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@deprecated 4 | ---@module 5 | ---@name melon.math 6 | ---- 7 | ---- Misc math functions 8 | ---- 9 | melon.math = melon.math or {} 10 | 11 | ---- 12 | ---@name melon.math.max 13 | ---- 14 | ---@arg (tbl: table) Table to get the max of 15 | ---@arg (key: any) Check a specific key if valid, otherwise all, seems useless? 16 | ---@return (max: number) Max value from the table 17 | ---- 18 | ---- math.max on all table elements 19 | ---- 20 | function melon.math.max(tbl, key) 21 | local cur = 0 22 | 23 | for k,v in pairs(tbl) do 24 | cur = math.max(cur, (key and tbl[key]) or v) 25 | end 26 | 27 | return cur 28 | end 29 | 30 | ---- 31 | ---@name melon.math.min 32 | ---- 33 | ---@arg (tbl: table) Table to get the min of 34 | ---@arg (key: any) Check a specific key if valid, otherwise all, seems useless? 35 | ---@return (max: number) Min value from the table 36 | ---- 37 | ---- math.min on all table elements 38 | ---- 39 | function melon.math.min(tbl, key) 40 | local cur = 0 41 | 42 | for k,v in pairs(tbl) do 43 | cur = math.min(cur, (key and tbl[key]) or v) 44 | end 45 | 46 | return cur 47 | end 48 | 49 | ---- 50 | ---@name melon.math.distance 51 | ---- 52 | ---@arg (x: number) X Coordinate 53 | ---@arg (y: number) Y Coordinate 54 | ---- 55 | ---- Gets the distance between x and y 56 | ---- 57 | function melon.math.distance(x, y) 58 | return math.abs(x - y) 59 | end 60 | 61 | ---- 62 | ---@name melon.math.sum 63 | ---- 64 | ---@arg (table: table) Table to get the sum of 65 | ---@return (sum: number) Sum of all the tables contents 66 | ---- 67 | ---- Gets the sum of an entire table 68 | ---- 69 | function melon.math.sum(t) 70 | local s = 0 71 | 72 | for i = 1, #t do 73 | s = s + t[i] 74 | end 75 | 76 | return s 77 | end -------------------------------------------------------------------------------- /lua/melon/core/misc/sh_modules.lua: -------------------------------------------------------------------------------- 1 | 2 | melon.Modules = melon.Modules or {} 3 | 4 | ---- 5 | ---@name melon.MODULE 6 | ---- 7 | ---@arg (name: string) Module to get the object of 8 | ---@return (mod: melon.ModuleObject) Module object of the name 9 | ---- 10 | ---- Get the [melon.ModuleObject] of the given name if it exists. 11 | ---- 12 | function melon.MODULE(name) 13 | return melon.Modules[name] 14 | end 15 | 16 | local M = {} 17 | M.__index = M 18 | AccessorFunc(M, "name", "Name", FORCE_STRING) 19 | AccessorFunc(M, "desc", "Description", FORCE_STRING) 20 | AccessorFunc(M, "ident", "ID", FORCE_STRING) 21 | 22 | function M:_call(name, ...) 23 | if self[name] then 24 | self[name](self, ...) 25 | end 26 | end 27 | 28 | function M:CommitHash() 29 | if SERVER then 30 | file.AsyncRead("addons/" .. self.ident .. "/.git/refs/heads/main", "GAME", function(_, _, _, data) 31 | if data then 32 | SetGlobal2String("melon_commit_hash:" .. self.ident, data:Trim()) 33 | end 34 | end) 35 | end 36 | 37 | return GetGlobal2String("melon_commit_hash:" .. self.ident) or "" 38 | end 39 | 40 | hook.Add("Melon:Debug", "ReloadHashes", function() 41 | for k,v in pairs(melon.Modules) do 42 | v:CommitHash() 43 | end 44 | end ) 45 | 46 | ---- 47 | ---@internal 48 | ---@name melon.ProcessExtras 49 | ---- 50 | ---- Processes the `extras` parameter of a Module Manifest 51 | ---- 52 | function melon.ProcessExtras(tbl) 53 | if not tbl then return { 54 | preload = {}, 55 | postload = {}, 56 | } end 57 | 58 | local ret = { 59 | preload = {}, 60 | postload = {} 61 | } 62 | 63 | for k,v in pairs(tbl) do 64 | if string.StartsWith(v, "postload:") then 65 | table.insert(ret.postload, string.Replace(v, "postload:", "")) 66 | continue 67 | end 68 | 69 | table.insert(ret.preload, v) 70 | end 71 | 72 | return ret 73 | end 74 | 75 | ---- 76 | ---@name melon.LoadModule 77 | ---- 78 | ---@arg (folder: string) Module folder name to load 79 | ---- 80 | ---- Loads a module from modules/ dynamically, reading __init__ and everything else. 81 | ---- 82 | function melon.LoadModule(fold) 83 | if not file.Exists("melon/modules/" .. fold .. "/__init__.lua", "LUA") then 84 | melon.Log(1, "Invalid Module '{1}', __init__.lua not found!", fold) 85 | return 86 | end 87 | 88 | local m = setmetatable({}, M) 89 | m:SetID(fold) 90 | m:SetName(fold) 91 | m:CommitHash() 92 | melon.Modules[fold] = m 93 | 94 | AddCSLuaFile("melon/modules/" .. fold .. "/__init__.lua") 95 | local incs = include("melon/modules/" .. fold .. "/__init__.lua") 96 | 97 | incs = incs or {} 98 | 99 | if incs.global then 100 | _G[incs.global] = m 101 | end 102 | 103 | local extras = melon.ProcessExtras(incs.extras) 104 | 105 | for k,v in pairs(extras.preload) do 106 | melon.LoadDirectory("melon/modules/" .. fold .. "/" .. v, v) 107 | m:_call("loaded_" .. v) 108 | melon.Log(3, "Loaded module extra preload:'{1}' successfully!", v) 109 | end 110 | 111 | if incs.recursive then 112 | melon.LoadDirectory("melon/modules/" .. fold .. "/src", fold) 113 | m:_call("loaded") 114 | melon.Log(3, "Loaded Module '{1}' successfully (recursive)!", fold) 115 | 116 | for k,v in pairs(extras.postload) do 117 | melon.LoadDirectory("melon/modules/" .. fold .. "/" .. v, v) 118 | m:_call("loaded_" .. v) 119 | melon.Log(3, "Loaded module extra postload:'{1}' successfully!", v) 120 | end 121 | 122 | return 123 | end 124 | 125 | incs.shared = incs.sh or incs.shared or {} 126 | incs.server = incs.sv or incs.server or {} 127 | incs.client = incs.cl or incs.client or {} 128 | 129 | -- Shared 130 | for k,v in pairs(incs.shared) do 131 | include("melon/modules/" .. fold .. "/" .. v) 132 | end 133 | 134 | -- Server 135 | if SERVER then 136 | for k,v in pairs(incs.server) do 137 | include("melon/modules/" .. fold .. "/" .. v) 138 | end 139 | end 140 | 141 | -- Client 142 | local f = (SERVER and AddCSLuaFile or include) 143 | for k,v in pairs(incs.client) do 144 | f("melon/modules/" .. fold .. "/" .. v) 145 | end 146 | 147 | for k,v in pairs(extras.postload) do 148 | melon.LoadDirectory("melon/modules/" .. fold .. "/" .. v, v) 149 | m:_call("loaded_" .. v) 150 | melon.Log(3, "Loaded module extra postload:'{1}' successfully!", v) 151 | end 152 | 153 | m:_call("loaded") 154 | melon.Log(3, "Loaded Module '{1}' successfully!", fold) 155 | end -------------------------------------------------------------------------------- /lua/melon/core/misc/sv_clearcmd.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@concommand cleer 4 | ---- 5 | ---- "Clears" the console by spamming it with a bunch of empty space 6 | ---- 7 | concommand.Add("cleer", function(ply) 8 | if IsValid(ply) then return end 9 | if CLIENT then return end 10 | 11 | melon.clr() 12 | end) -------------------------------------------------------------------------------- /lua/melon/core/misc/sv_update.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@name melon.ParseVersion 4 | ---- 5 | ---@arg (text: string) A string of 3 parts separated by . 6 | ---@return (vers: table) Table that contains 3 members, major, minor and patch 7 | ---- 8 | ---- Parses a string in the format major.minor.patch, 1.0.0, 45.56.67 into the version itself 9 | ---- 10 | function melon.ParseVersion(text) 11 | if not text then return end 12 | 13 | local split = string.Split(text, ".") 14 | 15 | local major = tonumber(split[1]) 16 | local minor = tonumber(split[2]) 17 | local patch = tonumber(split[3]) 18 | 19 | if not major or not minor or not patch then return end 20 | 21 | return { 22 | major = major, 23 | minor = minor, 24 | patch = patch, 25 | } 26 | end 27 | 28 | melon.UpdateLog = melon.AddDynamicLogHandler(function(msg) 29 | MsgC(Color(162, 0, 255), "[MelonLib][Update(" .. melon.version .. ")] ", color_white, msg.message, "\n") 30 | end ) 31 | 32 | melon.Log(melon.UpdateLog, "Checking for updates") 33 | 34 | ---- 35 | ---@internal 36 | ---@name melon.CheckForUpdates 37 | ---- 38 | function melon.CheckForUpdates(url, err) 39 | melon.http.Get(url, function(bod) 40 | local ver_text = string.gmatch(bod, "melon.version = \"([^\"]+)")() 41 | local v = melon.ParseVersion( 42 | ver_text 43 | ) 44 | local ov = melon.ParseVersion(melon.version) 45 | 46 | if not v then 47 | err(true) 48 | return 49 | end 50 | 51 | if v.major > ov.major then 52 | melon.Log(melon.UpdateLog, "New release update found, please update ASAP (-> {1})", ver_text) 53 | end 54 | 55 | if v.minor > ov.minor then 56 | melon.Log(melon.UpdateLog, "New feature update found, update whenever you can (-> {1})", ver_text) 57 | end 58 | 59 | if v.patch > ov.patch then 60 | melon.Log(melon.UpdateLog, "New patch update found, update only if needed (-> {1})", ver_text) 61 | end 62 | 63 | melon.Log(melon.UpdateLog, "Youre all up to date!", melon.version) 64 | end, function() 65 | err() 66 | end ) 67 | end 68 | 69 | local init_url = "https://raw.githubusercontent.com/melonstuff/melonlib/main/lua/autorun/sh_melon_lib_init.lua" 70 | local backup = "https://raw.githubusercontent.com/garryspins/melonlib/main/lua/autorun/sh_melon_lib_init.lua" 71 | melon.CheckForUpdates(init_url, function() 72 | melon.CheckForUpdates(backup, function(bad) 73 | if bad then 74 | melon.Log(melon.UpdateLog, "Could not find a valid version from the given text ({1})", ver_text) 75 | melon.Log(1, "Read previous error regarding Updating") 76 | end 77 | 78 | melon.Log(melon.UpdateLog, "Couldnt connect to github repository to find newer version ({1}), please report issue", init_url) 79 | melon.Log(1, "Read previous error regarding Updating") 80 | end ) 81 | 82 | melon.Log(melon.UpdateLog, "You should never see this post-transfer.") 83 | end ) -------------------------------------------------------------------------------- /lua/melon/core/thirdparty/cl_melons_masks.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --- Melon's Masks 3 | --- https://github.com/melonstuff/melonsmasks/ 4 | --- Licensed under MIT 5 | --- 6 | 7 | ---- 8 | ---@module 9 | ---@name masks 10 | ---@realm CLIENT 11 | ---- 12 | ---- An alternative to stencils that samples a texture 13 | ---- For reference: 14 | ---- The destination is what is being masked, so a multi stage gradient or some other complex stuff 15 | ---- The source is the text, or the thing with alpha 16 | ---- 17 | melon.masks = {} 18 | 19 | melon.masks.source = {} 20 | melon.masks.dest = {} 21 | 22 | melon.masks.source.rt = GetRenderTargetEx("MelonMasks_Source", ScrW(), ScrH(), RT_SIZE_NO_CHANGE, MATERIAL_RT_DEPTH_SEPARATE, bit.bor(1, 256), 0, IMAGE_FORMAT_BGRA8888) 23 | melon.masks.dest.rt = GetRenderTargetEx("MelonMasks_Destination", ScrW(), ScrH(), RT_SIZE_NO_CHANGE, MATERIAL_RT_DEPTH_SEPARATE, bit.bor(1, 256), 0, IMAGE_FORMAT_BGRA8888) 24 | 25 | melon.masks.source.mat = CreateMaterial("MelonMasks_Source", "UnlitGeneric", { 26 | ["$basetexture"] = melon.masks.source.rt:GetName(), 27 | ["$translucent"] = "1", 28 | ["$vertexalpha"] = "1", 29 | ["$vertexcolor"] = "1", 30 | }) 31 | melon.masks.dest.mat = CreateMaterial("MelonMasks_Destination", "UnlitGeneric", { 32 | ["$basetexture"] = melon.masks.dest.rt:GetName(), 33 | ["$translucent"] = "1", 34 | ["$vertexalpha"] = "1", 35 | ["$vertexcolor"] = "1", 36 | }) 37 | 38 | 39 | ---- 40 | ---@enumeration 41 | ---@name melon.masks.KIND 42 | ---- 43 | ---@enum (CUT) Cuts the source out of the destination 44 | ---@enum (STAMP) Cuts the destination out of the source 45 | ---- 46 | ---- Determines the type of mask were rendering 47 | ---- 48 | melon.masks.KIND_CUT = {BLEND_ZERO, BLEND_SRC_ALPHA, BLENDFUNC_ADD} 49 | melon.masks.KIND_STAMP = {BLEND_ZERO, BLEND_ONE_MINUS_SRC_ALPHA, BLENDFUNC_ADD} 50 | 51 | ---- 52 | ---@name melon.masks.Start 53 | ---- 54 | ---- 55 | ---- Starts the mask destination render 56 | ---- Whats between this and the `melon.masks.Source` call is the destination 57 | ---- See the module declaration for an explaination 58 | ---- 59 | function melon.masks.Start() 60 | render.PushRenderTarget(melon.masks.dest.rt) 61 | render.Clear(0, 0, 0, 0, true, true) 62 | cam.Start2D() 63 | end 64 | 65 | ---- 66 | ---@name melon.masks.Source 67 | ---- 68 | ---- Stops the destination render 69 | ---- Whats between this and the `melon.masks.End` call is the source 70 | ---- See the module declaration for an explaination 71 | ---- 72 | function melon.masks.Source() 73 | cam.End2D() 74 | render.PopRenderTarget() 75 | 76 | render.PushRenderTarget(melon.masks.source.rt) 77 | render.Clear(0, 0, 0, 0, true, true) 78 | cam.Start2D() 79 | end 80 | 81 | ---- 82 | ---@name melon.masks.And 83 | ---- 84 | ---@arg (kind: melon.masks.KIND_) The kind of mask this is, remember this is not a number enum 85 | ---- 86 | ---- Renders the given kind of mask and continues the mask render 87 | ---- This can be used to layer masks 88 | ---- This must be called post [melon.masks.Source] 89 | ---- You still need to call End 90 | ---- 91 | function melon.masks.And(kind) 92 | cam.End2D() 93 | render.PopRenderTarget() 94 | 95 | render.PushRenderTarget(melon.masks.dest.rt) 96 | cam.Start2D() 97 | render.OverrideBlend(true, 98 | kind[1], kind[2], kind[3] 99 | ) 100 | surface.SetDrawColor(255, 255, 255) 101 | surface.SetMaterial(melon.masks.source.mat) 102 | surface.DrawTexturedRect(0, 0, ScrW(), ScrH()) 103 | render.OverrideBlend(false) 104 | melon.masks.Source() 105 | end 106 | 107 | ---- 108 | ---@name melon.masks.End 109 | ---- 110 | ---@arg (kind: melon.masks.KIND_) The kind of mask this is, remember this is not a number enum 111 | ---@arg (x: number) The x coordinate to render the rectangle at, defaults to 0 112 | ---@arg (y: number) The y coordinate to render the rectangle at, defaults to 0 113 | ---@arg (w: number) The width of the rectangle to render 114 | ---@arg (h: number) The height of the rectangle to render 115 | ---- 116 | ---- Stops the source render and renders everything finally 117 | ---- See the module declaration for an explaination 118 | ---- 119 | function melon.masks.End(kind, x, y, w, h) 120 | kind = kind or melon.masks.KIND_CUT 121 | 122 | cam.End2D() 123 | render.PopRenderTarget() 124 | 125 | render.PushRenderTarget(melon.masks.dest.rt) 126 | cam.Start2D() 127 | render.OverrideBlend(true, 128 | kind[1], kind[2], kind[3] 129 | ) 130 | surface.SetDrawColor(255, 255, 255) 131 | surface.SetMaterial(melon.masks.source.mat) 132 | surface.DrawTexturedRect(0, 0, ScrW(), ScrH()) 133 | render.OverrideBlend(false) 134 | cam.End2D() 135 | render.PopRenderTarget() 136 | 137 | surface.SetDrawColor(255, 255, 255) 138 | surface.SetMaterial(melon.masks.dest.mat) 139 | surface.DrawTexturedRect(x or 0, y or 0, w or ScrW(), h or ScrH()) 140 | end 141 | 142 | ---- 143 | ---@name melon.masks.EndToTexture 144 | ---- 145 | ---@arg (tex: ITexture) 146 | ---@arg (kind: melon.masks.KIND_) The kind of mask this is, remember this is not a number enum 147 | ---- 148 | ---- Stops the source render and renders everything to the given ITexture 149 | ---- 150 | function melon.masks.EndToTexture(texture, kind) 151 | kind = kind or melon.masks.KIND_CUT 152 | 153 | cam.End2D() 154 | render.PopRenderTarget() 155 | 156 | render.PushRenderTarget(melon.masks.dest.rt) 157 | cam.Start2D() 158 | render.OverrideBlend(true, 159 | kind[1], kind[2], kind[3] 160 | ) 161 | surface.SetDrawColor(255, 255, 255) 162 | surface.SetMaterial(melon.masks.source.mat) 163 | surface.DrawTexturedRect(0, 0, ScrW(), ScrH()) 164 | render.OverrideBlend(false) 165 | cam.End2D() 166 | render.PopRenderTarget() 167 | 168 | render.CopyTexture(melon.masks.dest.rt, texture) 169 | end -------------------------------------------------------------------------------- /lua/melon/core/thirdparty/cl_melons_rounded_boxes.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | [Melon's Rounded Boxes] 3 | Want a rounded box that has a material? 4 | Want it to look nice? 5 | Want also to be able to draw any form of rounded polygons? 6 | WELL YOURE IN LUCK! 7 | Remember to cache your polygons 8 | and to credit me :) (https://github.com/garryspins) 9 | This doesnt stop you from doing stupid stuff, so dont be stupid 10 | ]]-- 11 | 12 | melon.thirdparty = melon.thirdparty or {} 13 | melon.thirdparty.RoundedBoxes = {} 14 | 15 | ---- 16 | ---@name melon.thirdparty.RoundedBoxes.RoundedBox 17 | ---- 18 | ---@arg (radius: number) Radius of the rounded box 19 | ---@arg (x: number) X position of the box 20 | ---@arg (y: number) Y position of the box 21 | ---@arg (w: number) W of the box 22 | ---@arg (h: number) H of the box 23 | ---@arg (bl: number) Should the bottom left be rounded independently, if so how much 24 | ---@arg (tl: number) Should the top left be rounded independently, if so how much 25 | ---@arg (tr: number) Should the top right be rounded independently, if so how much 26 | ---@arg (br: number) Should the bottom right be rounded independently, if so how much 27 | ---@arg (detail: number) Number of vertices to put on edges, defaults to 1 for perfect quality, raise this number for how many vertices to skip. 28 | ---- 29 | ---@return (poly: table) Polygon to be drawn with surface.DrawPoly 30 | ---- 31 | ---- Generates a rounded box polygon 32 | ---- 33 | ---` 34 | ---` local mat = Material("vgui/gradient-l") 35 | ---` local PANEL = vgui.Register("RoundedGradient", {}, "Panel") 36 | ---` 37 | ---` function PANEL:PerformLayout(w, h) 38 | ---` self.background = melon.thirdparty.RoundedBoxes.RoundedBox(w / 4, 0, 0, w, h) 39 | ---` end 40 | ---` 41 | ---` function PANEL:Paint(w, h) 42 | ---` draw.NoTexture() 43 | ---` surface.SetDrawColor(0, 89, 161) 44 | ---` surface.DrawPoly(self.background) 45 | ---` 46 | ---` surface.SetMaterial(mat) 47 | ---` surface.SetDrawColor(0, 140, 255) 48 | ---` surface.DrawPoly(self.background) 49 | ---` end 50 | ---` 51 | function melon.thirdparty.RoundedBoxes.RoundedBox(radius, x, y, w, h, bl, tl, tr, br, detail) 52 | local unround = { 53 | { 54 | x = x, 55 | y = y + h, 56 | u = 0, 57 | v = 1, 58 | radius = isnumber(bl) and bl 59 | }, 60 | { 61 | x = x, 62 | y = y, 63 | u = 0, 64 | v = 0, 65 | radius = isnumber(tl) and tl 66 | }, 67 | { 68 | x = x + w, 69 | y = y, 70 | u = 1, 71 | v = 0, 72 | radius = isnumber(tr) and tr 73 | }, 74 | { 75 | x = x + w, 76 | y = y + h, 77 | u = 1, 78 | v = 1, 79 | radius = isnumber(br) and br 80 | }, 81 | } 82 | 83 | return melon.thirdparty.RoundedBoxes.RoundedPolygonUV(unround, radius, x, y, w, h, detail) 84 | end 85 | 86 | function melon.thirdparty.RoundedBoxes.RoundedPolygonUV(poly, default_radius, x,y,w,h, detail) 87 | poly = melon.thirdparty.RoundedBoxes.RoundedPolygon(poly, default_radius, detail) 88 | 89 | for k,v in pairs(poly) do 90 | v.u = (v.x-x) / w 91 | v.v = (v.y-y) / h 92 | end 93 | 94 | return poly 95 | end 96 | 97 | function melon.thirdparty.RoundedBoxes.RoundedPolygon(poly, default_radius, detail) 98 | local points = {} 99 | 100 | for k,v in pairs(poly) do 101 | local last = poly[k - 1] or poly[#poly] 102 | local curr = v 103 | local next = poly[k + 1] or poly[1] 104 | 105 | local radius = curr.radius or default_radius 106 | if radius == 0 then 107 | table.insert(points, curr) 108 | continue end 109 | 110 | local ltc_ang = math.atan2(curr.y - last.y, curr.x - last.x) + math.rad(180) 111 | local ntc_ang = math.atan2(curr.y - next.y, curr.x - next.x) + math.rad(180) 112 | 113 | local lex, ley = math.cos(ltc_ang) * radius, math.sin(ltc_ang) * radius 114 | local nex, ney = math.cos(ntc_ang) * radius, math.sin(ntc_ang) * radius 115 | 116 | local cx, cy = curr.x + nex + lex, curr.y + ney + ley, 117 | 118 | table.insert(points, { 119 | x = curr.x + lex, 120 | y = curr.y + ley, 121 | }) 122 | 123 | local range = math.deg(ltc_ang - ntc_ang) % 360 124 | for i = 1, range - 1, detail or 1 do 125 | table.insert(points, { 126 | x = cx + math.cos(ntc_ang + math.rad(i + 180)) * radius, 127 | y = cy + math.sin(ntc_ang + math.rad(i + 180)) * radius, 128 | }) 129 | end 130 | 131 | table.insert(points, { 132 | x = curr.x + nex, 133 | y = curr.y + ney, 134 | }) 135 | end 136 | 137 | return points 138 | end -------------------------------------------------------------------------------- /lua/melon/core/thirdparty/cl_melons_shadows.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --- Melon's Shadows 3 | --- Can be found at: 4 | --- https://github.com/melonstuff/melonsshadows/ 5 | --- 6 | 7 | local shadows = {} 8 | 9 | shadows.RenderTarget = GetRenderTarget("MelonsShadows", ScrW(), ScrH())shadows.Material = CreateMaterial("MelonsShadows", "UnlitGeneric", {["$basetexture"] = shadows.RenderTarget:GetName(),["$transparent"] = "1",["$color"] = "1",["$vertexalpha"] = "1",["$alpha"] = "1"})shadows.Current = false shadows.LastRendered = false function shadows.Start(name)if shadows.LastRendered and (shadows.LastRendered == name) and shadows.Current then shadows.Render()return false end shadows.LastRendered = name shadows.Current = {opacity = 255,intensity = 1,spread = 5,blur = 1,relative = false,at_x = 0,at_y = 0,xoffset = 0,yoffset = 0 }shadows.Material:SetFloat("$alpha", 1)render.PushRenderTarget(shadows.RenderTarget)render.Clear(0, 0, 0, 0)cam.Start2D()return true end function shadows.End()local c = shadows.Current if not c then debug.Trace()return Error("[MelonsShadows] Attempting to end a shadow that hasnt been started\n")end if c.blur != 0 then render.BlurRenderTarget(shadows.RenderTarget, c.spread, c.spread, c.blur)end render.PopRenderTarget()shadows.Render()cam.End2D()end function shadows.Render()local c = shadows.Current shadows.Material:SetFloat("$alpha", c.opacity / 255)render.SetMaterial(shadows.Material)local x, y = 0, 0 local w, h = ScrW(), ScrH()if IsValid(c.relative) then x, y = c.relative:LocalToScreen(0, 0)x = x - c.at_x y = y - c.at_y end x = x + c.xoffset y = y + c.yoffset for i = 0, c.intensity do render.DrawScreenQuadEx(x, y, w, h)end end function shadows.StateAccessor(key)return function(value)shadows.Current[key] = value end end function shadows.Dirty()shadows.LastRendered = false end shadows.SetOpacity = shadows.StateAccessor("opacity")shadows.SetIntensity = shadows.StateAccessor("intensity")shadows.SetSpread = shadows.StateAccessor("spread")shadows.SetBlur = shadows.StateAccessor("blur")shadows.SetRelative = shadows.StateAccessor("relative")function shadows.SetRenderedAt(x, y)shadows.Current.at_x = x shadows.Current.at_y = y end function shadows.SetOffset(x, y)shadows.Dirty()shadows.Current.xoffset = x shadows.Current.yoffset = y end 10 | 11 | melon.thirdparty = melon.thirdparty or {} 12 | melon.thirdparty.Shadows = shadows -------------------------------------------------------------------------------- /lua/melon/core/thirdparty/credit.txt: -------------------------------------------------------------------------------- 1 | 2 | Circles! by SneakySquid, can be found at https://github.com/SneakySquid/Circles 3 | MIT License 4 | 5 | Copyright (c) 2019 Sneaky-Squid 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | ------------------------------------------------------------------------------ 26 | 27 | material-avatar by Billy, can be found at https://github.com/WilliamVenner/glua-material-avatar 28 | MIT License 29 | 30 | Copyright (c) 2021 William Venner 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in all 40 | copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 48 | SOFTWARE. 49 | 50 | ------------------------------------------------------------------------------ 51 | 52 | ui3d2d by TomDotBat, can be found at https://github.com/TomDotBat/ui3d2d 53 | 54 | ------------------------------------------------------------------------------ 55 | 56 | blues_shadows by CodeBlue, can be found at https://gist.githubusercontent.com/MysteryPancake/a31637af9fd531079236a2577145a754/raw/c21965aed15b4ddddec7b6c40ce32e0a89d425db/bluesshadows.lua 57 | RIP 58 | 59 | ------------------------------------------------------------------------------ 60 | 61 | Melons Rounded Boxes by MustBeLeaving/Melon... me! can be found at https://gist.github.com/garryspins/cbe49806f1f593a69a7f13becd60bfa0 62 | 63 | ------------------------------------------------------------------------------ 64 | 65 | -- Copyright 2012 Rackspace (original), 2013-2021 Thijs Schreijer (modifications) 66 | -- 67 | -- Licensed under the Apache License, Version 2.0 (the "License"); 68 | -- you may not use this file except in compliance with the License. 69 | -- You may obtain a copy of the License at 70 | -- 71 | -- http://www.apache.org/licenses/LICENSE-2.0 72 | -- 73 | -- Unless required by applicable law or agreed to in writing, software 74 | -- distributed under the License is distributed on an "AS-IS" BASIS, 75 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 76 | -- See the License for the specific language governing permissions and 77 | -- limitations under the License. 78 | -- 79 | -- see http://www.ietf.org/rfc/rfc4122.txt 80 | -- 81 | -- Note that this is not a true version 4 (random) UUID. Since `os.time()` precision is only 1 second, it would be hard 82 | -- to guarantee spacial uniqueness when two hosts generate a uuid after being seeded during the same second. This 83 | -- is solved by using the node field from a version 1 UUID. It represents the mac address. 84 | -- 85 | -- 28-apr-2013 modified by Thijs Schreijer from the original [Rackspace code](https://github.com/kans/zirgo/blob/807250b1af6725bad4776c931c89a784c1e34db2/util/uuid.lua) as a generic Lua module. 86 | -- Regarding the above mention on `os.time()`; the modifications use the `socket.gettime()` function from LuaSocket 87 | -- if available and hence reduce that problem (provided LuaSocket has been loaded before uuid). 88 | -- 89 | -- **Important:** the random seed is a global piece of data. Hence setting it is 90 | -- an application level responsibility, libraries should never set it! 91 | -- 92 | -- See this issue; [https://github.com/Kong/kong/issues/478](https://github.com/Kong/kong/issues/478) 93 | -- It demonstrates the problem of using time as a random seed. Specifically when used from multiple processes. 94 | -- So make sure to seed only once, application wide. And to not have multiple processes do that 95 | -- simultaneously. -------------------------------------------------------------------------------- /lua/melon/core/thirdparty/material-avatar/cl_material-avatar.lua: -------------------------------------------------------------------------------- 1 | 2 | local AVATAR_IMAGE_CACHE_EXPIRES = 86400 -- 1 day, in seconds 3 | 4 | local function getAvatarMaterial(steamid64, callback) 5 | -- First, check the cache to see if this avatar has already been downloaded. 6 | -- If the avatar hasn't been cached in data/, file.Time will return 0. 7 | -- If an avatar material is 1 day old, let's redownload it but use it as a fallback in case something goes wrong. 8 | local fallback 9 | if os.time() - file.Time("avatars/" .. steamid64 .. ".png", "DATA") > AVATAR_IMAGE_CACHE_EXPIRES then 10 | fallback = Material("../data/avatars/" .. steamid64 .. ".png", "smooth") 11 | elseif os.time() - file.Exists("avatars/" .. steamid64 .. ".jpg", "DATA") > AVATAR_IMAGE_CACHE_EXPIRES then 12 | fallback = Material("../data/avatars/" .. steamid64 .. ".jpg", "smooth") 13 | end 14 | 15 | -- If a fallback couldn't be found in data/, default to vgui/avatar_default 16 | if not fallback or fallback:IsError() then 17 | fallback = Material("vgui/avatar_default") 18 | else 19 | -- Otherwise, if a cached avatar was found, and it hasn't expired, return it! 20 | return callback(fallback) 21 | end 22 | 23 | -- Fetch the XML version of the player's Steam profile. 24 | -- This XML contains a tag, which contains the URL to their full avatar. 25 | http.Fetch("https://steamcommunity.com/profiles/" .. steamid64 .. "?xml=1", 26 | 27 | function(body, size, headers, code) 28 | -- If the HTTP request fails (size = 0, code is not a HTTP success response code) then return the fallback 29 | if size == 0 or code < 200 or code > 299 then return callback(fallback, steamid64) end 30 | 31 | local url, fileType = body:match(".-(https?://%S+%f[%.]%.)(%w+).-") -- Extract the URL and file extension from 32 | if not url or not fileType then return callback(fallback, steamid64) end -- If the URL or file type couldn't be extracted, return the fallback. 33 | if fileType == "jpeg" then fileType = "jpg" end -- Defensively ensure jpeg -> jpg. 34 | 35 | -- Download the avatar image 36 | http.Fetch(url .. fileType, 37 | 38 | function(body, size, headers, code) 39 | if size == 0 or code < 200 or code > 299 then return callback(fallback, steamid64) end 40 | 41 | local cachePath = "avatars/" .. steamid64 .. "." .. fileType 42 | file.CreateDir("avatars") 43 | file.Write(cachePath, body) -- Write the avatar to data/ 44 | 45 | local material = Material("../data/" .. cachePath, "smooth") -- Load the avatar from data/ as a Material 46 | if material:IsError() then 47 | -- If the material errors, the image must be corrupt, so we'll delete this from data/ and return the fallback. 48 | file.Delete(cachePath) 49 | callback(fallback, steamid64) 50 | else 51 | -- We succeeded, return the downloaded avatar image material! 52 | callback(material, steamid64) 53 | end 54 | 55 | end, 56 | 57 | -- If we hard-fail, return the fallback image. 58 | function() callback(fallback, steamid64) end 59 | 60 | ) 61 | end, 62 | 63 | -- If we hard-fail, return the fallback image. 64 | function() callback(fallback, steamid64) end 65 | ) 66 | end 67 | 68 | -- We don't want to fill the user's hard drive up with avatars over time, so we'll clear them whenever they join the server. 69 | -- This also has the added benefit of allowing the user to "manually" regenerate avatars if they so desire. 70 | local function clearCachedAvatars() 71 | for _, f in ipairs( ( file.Find("avatars/*", "DATA") ) ) do 72 | file.Delete("avatars/" .. f) 73 | end 74 | 75 | hook.Remove("InitPostEntity", "clearCachedAvatars") -- Just to be safe. 76 | end 77 | 78 | hook.Add("InitPostEntity", "clearCachedAvatars", clearCachedAvatars) 79 | 80 | melon.thirdparty = melon.thirdparty or {} 81 | melon.thirdparty.getAvatarMaterial = getAvatarMaterial -------------------------------------------------------------------------------- /lua/melon/core/thirdparty/material-avatar/cl_vgui-element.lua: -------------------------------------------------------------------------------- 1 | melon.thirdparty = melon.thirdparty or {} 2 | local getAvatarMaterial = melon.thirdparty.getAvatarMaterial 3 | 4 | local PANEL = {} 5 | 6 | function PANEL:Init() 7 | getAvatarMaterial = melon.thirdparty.getAvatarMaterial 8 | self.m_Material = Material("vgui/avatar_default") 9 | end 10 | 11 | function PANEL:SetPlayer(ply) 12 | if ply:IsBot() then return end 13 | self.m_SteamID64 = ply:SteamID64() 14 | self:Download() 15 | end 16 | 17 | function PANEL:SetSteamID64(steamid64) 18 | self.m_SteamID64 = steamid64 19 | if steamid64 then 20 | self:Download() 21 | end 22 | end 23 | 24 | function PANEL:GetPlayer() 25 | if self.m_SteamID64 == nil then 26 | return NULL 27 | else 28 | -- This is slow. Don't call it every frame. 29 | return player.GetBySteamID64(self.m_SteamID64) 30 | end 31 | end 32 | 33 | function PANEL:GetMaterial() 34 | return self.m_Material 35 | end 36 | 37 | function PANEL:GetSteamID64() 38 | return self.m_SteamID64 39 | end 40 | 41 | function PANEL:Download() 42 | assert(self.m_SteamID64 ~= nil, "Tried to download the avatar image of a nil SteamID64!") 43 | getAvatarMaterial(self.m_SteamID64, function(mat) 44 | if not IsValid(self) then return end -- The panel could've been destroyed before it could download the avatar image. 45 | self.m_Material = mat 46 | end) 47 | end 48 | 49 | function PANEL:Paint(w, h) 50 | surface.SetDrawColor(255, 255, 255) 51 | surface.SetMaterial(self.m_Material) 52 | surface.DrawTexturedRect(0, 0, w, h) 53 | end 54 | 55 | vgui.Register("Melon:AvatarMaterial", PANEL, "Panel") -------------------------------------------------------------------------------- /lua/melon/core/thirdparty/sh_uuid.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local bitsize = 32 -- bitsize assumed for Lua VM. See randomseed function below. 4 | local lua_version = tonumber(_VERSION:match("%d%.*%d*")) -- grab Lua version used 5 | 6 | local MATRIX_AND = {{0,0},{0,1} } 7 | local MATRIX_OR = {{0,1},{1,1}} 8 | local HEXES = "0123456789abcdef" 9 | 10 | local math_floor = math.floor 11 | local math_random = math.random 12 | local math_abs = math.abs 13 | local string_sub = string.sub 14 | local to_number = tonumber 15 | local assert = assert 16 | local type = type 17 | 18 | -- performs the bitwise operation specified by truth matrix on two numbers. 19 | local function BITWISE(x, y, matrix) 20 | local z = 0 21 | local pow = 1 22 | while x > 0 or y > 0 do 23 | z = z + (matrix[x % 2 + 1][y % 2 + 1] * pow) 24 | pow = pow * 2 25 | x = math_floor(x / 2) 26 | y = math_floor(y / 2) 27 | end 28 | return z 29 | end 30 | 31 | local function INT2HEX(x) 32 | local s,base = '',16 33 | local d 34 | while x > 0 do 35 | d = x % base + 1 36 | x = math_floor(x / base) 37 | s = string_sub(HEXES, d, d) .. s 38 | end 39 | while #s < 2 do s = "0" .. s end 40 | return s 41 | end 42 | 43 | ---------------------------------------------------------------------------- 44 | -- Creates a new uuid. Either provide a unique hex string, or make sure the 45 | -- random seed is properly set. The module table itself is a shortcut to this 46 | -- function, so `my_uuid = uuid.new()` equals `my_uuid = uuid()`. 47 | -- 48 | -- For proper use there are 3 options; 49 | -- 50 | -- 1. first require `luasocket`, then call `uuid.seed()`, and request a uuid using no 51 | -- parameter, eg. `my_uuid = uuid()` 52 | -- 2. use `uuid` without `luasocket`, set a random seed using `uuid.randomseed(some_good_seed)`, 53 | -- and request a uuid using no parameter, eg. `my_uuid = uuid()` 54 | -- 3. use `uuid` without `luasocket`, and request a uuid using an unique hex string, 55 | -- eg. `my_uuid = uuid(my_networkcard_macaddress)` 56 | -- 57 | -- @return a properly formatted uuid string 58 | -- @param hwaddr (optional) string containing a unique hex value (e.g.: `00:0c:29:69:41:c6`), to be used to compensate for the lesser `math_random()` function. Use a mac address for solid results. If omitted, a fully randomized uuid will be generated, but then you must ensure that the random seed is set properly! 59 | -- @usage 60 | -- local uuid = require("uuid") 61 | -- print("here's a new uuid: ",uuid()) 62 | function M.new(hwaddr) 63 | -- bytes are treated as 8bit unsigned bytes. 64 | local bytes = { 65 | math_random(0, 255), 66 | math_random(0, 255), 67 | math_random(0, 255), 68 | math_random(0, 255), 69 | math_random(0, 255), 70 | math_random(0, 255), 71 | math_random(0, 255), 72 | math_random(0, 255), 73 | math_random(0, 255), 74 | math_random(0, 255), 75 | math_random(0, 255), 76 | math_random(0, 255), 77 | math_random(0, 255), 78 | math_random(0, 255), 79 | math_random(0, 255), 80 | math_random(0, 255) 81 | } 82 | 83 | if hwaddr then 84 | assert(type(hwaddr) == "string", "Expected hex string, got " .. type(hwaddr)) 85 | -- Cleanup provided string, assume mac address, so start from back and cleanup until we've got 12 characters 86 | local i,str = #hwaddr, hwaddr 87 | hwaddr = "" 88 | while i > 0 and #hwaddr < 12 do 89 | local c = str:sub(i,i):lower() 90 | if HEXES:find(c, 1, true) then 91 | -- valid HEX character, so append it 92 | hwaddr = c .. hwaddr 93 | end 94 | i = i - 1 95 | end 96 | assert(#hwaddr == 12, "Provided string did not contain at least 12 hex characters, retrieved '" .. hwaddr .. "' from '" .. str .. "'") 97 | 98 | -- no split() in lua. :( 99 | bytes[11] = to_number(hwaddr:sub(1, 2), 16) 100 | bytes[12] = to_number(hwaddr:sub(3, 4), 16) 101 | bytes[13] = to_number(hwaddr:sub(5, 6), 16) 102 | bytes[14] = to_number(hwaddr:sub(7, 8), 16) 103 | bytes[15] = to_number(hwaddr:sub(9, 10), 16) 104 | bytes[16] = to_number(hwaddr:sub(11, 12), 16) 105 | end 106 | 107 | -- set the version 108 | bytes[7] = BITWISE(bytes[7], 0x0f, MATRIX_AND) 109 | bytes[7] = BITWISE(bytes[7], 0x40, MATRIX_OR) 110 | -- set the variant 111 | bytes[9] = BITWISE(bytes[9], 0x3f, MATRIX_AND) 112 | bytes[9] = BITWISE(bytes[9], 0x80, MATRIX_OR) 113 | return INT2HEX(bytes[1 ]) .. INT2HEX(bytes[2 ]) .. INT2HEX(bytes[3]) .. INT2HEX(bytes[4]) .. "-" .. 114 | INT2HEX(bytes[5 ]) .. INT2HEX(bytes[6 ]) .. "-" .. 115 | INT2HEX(bytes[7 ]) .. INT2HEX(bytes[8 ]) .. "-" .. 116 | INT2HEX(bytes[9 ]) .. INT2HEX(bytes[10]) .. "-" .. 117 | INT2HEX(bytes[11]) .. INT2HEX(bytes[12]) .. INT2HEX(bytes[13]) .. INT2HEX(bytes[14]) .. INT2HEX(bytes[15]) .. INT2HEX(bytes[16]) 118 | end 119 | 120 | ---------------------------------------------------------------------------- 121 | -- Improved randomseed function. 122 | -- Lua 5.1 and 5.2 both truncate the seed given if it exceeds the integer 123 | -- range. If this happens, the seed will be 0 or 1 and all randomness will 124 | -- be gone (each application run will generate the same sequence of random 125 | -- numbers in that case). This improved version drops the most significant 126 | -- bits in those cases to get the seed within the proper range again. 127 | -- @param seed the random seed to set (integer from 0 - 2^32, negative values will be made positive) 128 | -- @return the (potentially modified) seed used 129 | -- @usage 130 | -- local socket = require("socket") -- gettime() has higher precision than os.time() 131 | -- local uuid = require("uuid") 132 | -- -- see also example at uuid.seed() 133 | -- uuid.randomseed(socket.gettime()*10000) 134 | -- print("here's a new uuid: ",uuid()) 135 | function M.RandomSeed(seed) 136 | seed = math_floor(math_abs(seed)) 137 | if seed >= (2^bitsize) then 138 | -- integer overflow, so reduce to prevent a bad seed 139 | seed = seed - math_floor(seed / 2^bitsize) * (2^bitsize) 140 | end 141 | if lua_version < 5.2 then 142 | -- 5.1 uses (incorrect) signed int 143 | math.randomseed(seed - 2^(bitsize-1)) 144 | else 145 | -- 5.2 uses (correct) unsigned int 146 | math.randomseed(seed) 147 | end 148 | return seed 149 | end 150 | 151 | ---------------------------------------------------------------------------- 152 | -- Seeds the random generator. 153 | -- It does so in 3 possible ways; 154 | -- 155 | -- 1. if in ngx_lua, use `ngx.time() + ngx.worker.pid()` to ensure a unique seed 156 | -- for each worker. It should ideally be called from the `init_worker` context. 157 | -- 2. use luasocket `gettime()` function, but it only does so when LuaSocket 158 | -- has been required already. 159 | -- 3. use `os.time()`: this only offers resolution to one second (used when 160 | -- LuaSocket hasn't been loaded) 161 | -- 162 | -- **Important:** the random seed is a global piece of data. Hence setting it is 163 | -- an application level responsibility, libraries should never set it! 164 | -- @usage 165 | -- local socket = require("socket") -- gettime() has higher precision than os.time() 166 | -- -- LuaSocket loaded, so below line does the same as the example from randomseed() 167 | -- uuid.seed() 168 | -- print("here's a new uuid: ",uuid()) 169 | function M.Seed() 170 | return M.RandomSeed(SysTime() * 10000) 171 | end 172 | 173 | local UUID = setmetatable( M, { __call = function(self, hwaddr) return self.new(hwaddr) end} ) 174 | 175 | function melon.UUID() 176 | UUID.Seed() 177 | return UUID() 178 | end -------------------------------------------------------------------------------- /lua/melon/modules/template/__init__.lua: -------------------------------------------------------------------------------- 1 | 2 | return { 3 | extras = {"config"}, 4 | recursive = true, 5 | } -------------------------------------------------------------------------------- /lua/melon/modules/template/config/sh_config.lua: -------------------------------------------------------------------------------- 1 | 2 | return 123 -------------------------------------------------------------------------------- /lua/melon/modules/template/src/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melonstuff/melonlib/d86cb7a4bd3edd770c05638b92e96fc02e893224/lua/melon/modules/template/src/.gitkeep -------------------------------------------------------------------------------- /lua/melon/preload/sh_accessors.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@deprecated melon.AccessorFunc 4 | ---@name melon.AccessorTable 5 | ---- 6 | ---- Dont use this 7 | ---- 8 | function melon.AccessorTable(tbl, metatable) 9 | tbl = tbl or {} 10 | tbl.__index = tbl 11 | tbl.Accessor = function(s, name, default) 12 | AccessorFunc(s, "val_" .. name, name) 13 | s["val_" .. name] = default 14 | end 15 | tbl.New = function(s, ...) 16 | local m = setmetatable({}, s.__metatable) 17 | 18 | for k,v in pairs(s) do 19 | m[k] = v 20 | end 21 | 22 | if m.Init then 23 | m:Init(...) 24 | end 25 | 26 | return m 27 | end 28 | 29 | tbl.__metatable = metatable or {} 30 | setmetatable(tbl, tbl.__metatable) 31 | return tbl 32 | end 33 | 34 | ---- 35 | ---@silence 36 | ---@name melon.AT 37 | ---@alias melon.AccessorTable 38 | ---- 39 | melon.AT = melon.AccessorTable 40 | 41 | ---- 42 | ---@deprecated melon.AccessorFunc 43 | ---@name melon.AF 44 | ---- 45 | ---@arg (table: table) Table to add the accessor to 46 | ---@arg (name: string) String name for the accessor 47 | ---@arg (default: any) Default value for the accessor 48 | ---- 49 | ---- Good replacement for AccessorFunc 50 | ---- 51 | function melon.AF(t, name, default) 52 | t["v_" .. name] = default 53 | 54 | t[name] = function(s, v) 55 | if v == nil then 56 | return s["v_" .. name] 57 | end 58 | 59 | if t.__on_dirty then 60 | t:__on_dirty(name, v) 61 | end 62 | 63 | s["v_" .. name] = v 64 | return s 65 | end 66 | 67 | return function(...) 68 | return melon.AF(t, ...) 69 | end 70 | end 71 | 72 | ---- 73 | ---@name melon.AccessorFunc 74 | ---- 75 | ---@arg (table: table) Table to add the accessor to 76 | ---@arg (name: string) String name of the key 77 | ---@arg (default: any) Default value of the accessor key 78 | ---@arg (type: TYPE_) Type of the accessor 79 | ---- 80 | ---- Adds an accessor function to the given table and sets the default if needed 81 | ---- 82 | ---- Differences between AccessorFunc 83 | ---- - `tbl:Set*()` calls `tbl:OnAccessorChange(name, from, to)` if it exists on the given table 84 | ---- - `tbl:Set*()` returns `tbl` 85 | ---- - Restricts to a TYPE_ enum instead of a FORCE_ 86 | ---- 87 | function melon.AccessorFunc(tbl, name, def, type) 88 | tbl.__melon_accessors = tbl.__melon_accessors or {} 89 | tbl.__melon_accessors[name] = true 90 | 91 | tbl["Set" .. name] = function(s, value) 92 | if type and (TypeID(value) != type) then 93 | return 94 | end 95 | 96 | if s.OnAccessorChange then 97 | s:OnAccessorChange(name, s[name], value) 98 | end 99 | 100 | s[name] = value 101 | 102 | return s 103 | end 104 | 105 | tbl["Get" .. name] = function(s) 106 | return s[name] 107 | end 108 | 109 | if def then 110 | tbl[name] = def 111 | end 112 | end -------------------------------------------------------------------------------- /lua/melon/preload/sh_delay_http.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@module 4 | ---@name melon.http 5 | ---- 6 | ---- HTTP wrapper that runs http requests when available 7 | ---- 8 | melon.http = {} 9 | local requests = {} 10 | 11 | ---- 12 | ---@name melon.HTTP 13 | ---- 14 | ---@arg (data: table) [HTTPRequest] data to execute with 15 | ---- 16 | ---- Queues an HTTP request to run whenever available 17 | ---- 18 | function melon.HTTP(h) 19 | if not requests then 20 | return HTTP(h) 21 | end 22 | 23 | table.insert(requests, h) 24 | end 25 | 26 | ---- 27 | ---@internal 28 | ---@name melon.http.Generator 29 | ---- 30 | ---@arg (type: string) Type of HTTP request, POST, HEAD, GET, ect 31 | ---@return (func: func) Function that calls the given request using [melon.HTTP] 32 | ---- 33 | ---- Generates a new function to create a request of the given type 34 | ---- 35 | function melon.http.Generator(type) 36 | return function(url, onsuccess, onfailure, header) 37 | local request = { 38 | url = url, 39 | method = type, 40 | headers = header or {}, 41 | 42 | success = function(code, body, headers) 43 | if not onsuccess then return end 44 | onsuccess(body, body:len(), headers, code) 45 | end, 46 | 47 | failed = function(err) 48 | if not onfailure then return end 49 | 50 | onfailure(err) 51 | end 52 | } 53 | 54 | melon.HTTP(request) 55 | end 56 | end 57 | 58 | ---- 59 | ---@silence 60 | ---@type function 61 | ---@name melon.http.Post 62 | ---- 63 | ---@arg (url: string) URL to make the request to 64 | ---@arg (onsuccess: func) Callback to run on success, gets same as http.Post 65 | ---@arg (onfailure: func) Callback to run on failure, gets same as http.Post 66 | ---@arg (headers: table) URL to make the request to 67 | ---- 68 | ---- Make a POST request with melon.HTTP 69 | ---- 70 | melon.http.Post = melon.http.Generator("post") 71 | 72 | ---- 73 | ---@silence 74 | ---@type function 75 | ---@name melon.http.Get 76 | ---- 77 | ---@arg (url: string) URL to make the request to 78 | ---@arg (onsuccess: func) Callback to run on success, gets same as http.Post 79 | ---@arg (onfailure: func) Callback to run on failure, gets same as http.Post 80 | ---@arg (headers: table) URL to make the request to 81 | ---- 82 | ---- Make a GET request with melon.HTTP 83 | ---- 84 | melon.http.Get = melon.http.Generator("get") 85 | 86 | ---- 87 | ---@silence 88 | ---@type function 89 | ---@name melon.http.Head 90 | ---- 91 | ---@arg (url: string) URL to make the request to 92 | ---@arg (onsuccess: func) Callback to run on success, gets same as http.Post 93 | ---@arg (onfailure: func) Callback to run on failure, gets same as http.Post 94 | ---@arg (headers: table) URL to make the request to 95 | ---- 96 | ---- Make a HEAD request with melon.HTTP 97 | ---- 98 | melon.http.Head = melon.http.Generator("head") 99 | 100 | hook.Add("Think", "Melon:HTTPReady", function() 101 | for k,v in pairs(requests) do 102 | HTTP(v) 103 | end 104 | 105 | requests = false 106 | 107 | hook.Remove("Think", "Melon:HTTPReady") 108 | end ) -------------------------------------------------------------------------------- /lua/melon/preload/sh_files.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@internal 4 | ---@name melon.__file__ 5 | ---- 6 | ---- If you dont understand the source for this, dont use it. 7 | ---- 8 | function melon.__file__(relative, lvl) 9 | return debug.getinfo(lvl or 2).short_src:gsub(not relative and "^addons/melon%-lib/lua/" or "", "") 10 | end 11 | 12 | ---- 13 | ---@internal 14 | ---@name melon.__file_contents__ 15 | ---- 16 | ---- If you dont understand the source for this, dont use it. 17 | ---- 18 | function melon.__file_contents__(name, lvl) 19 | return file.Read(name or melon.__file__(false, lvl or 3), "LUA") 20 | end 21 | 22 | ---- 23 | ---@internal 24 | ---@name melon.__file_state__ 25 | ---- 26 | ---- If you dont understand the source for this, dont use it. 27 | ---- 28 | function melon.__file_state__(lvl, name) 29 | local l = name or string.Split(melon.__file__(true, (lvl or 1) + 1), "/") 30 | l = l[#l]:sub(1, 3) 31 | return (l == "sh_" and "shared") or (l == "cl_" and "client") or (l == "sv" and "server") or "unknown", l 32 | end -------------------------------------------------------------------------------- /lua/melon/preload/sh_function_attributes.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@silence 4 | ---@internal 5 | ---@deprecated 6 | ---@name melon.attr 7 | ---- 8 | ---- Weird experiment thats not really used, dont use. 9 | ---- 10 | melon.attr = setmetatable({}, { 11 | __call = function(s, name, fn) 12 | if not s[name] then 13 | print("[MelonLib] Attribute '" .. name .. "' not found!") 14 | return 15 | end 16 | 17 | return function(...) 18 | return s[name](fn, ...) 19 | end 20 | end 21 | }) 22 | -------------------------------------------------------------------------------- /lua/melon/preload/sh_index_behavior.lua: -------------------------------------------------------------------------------- 1 | 2 | local behavior = {} 3 | 4 | ---- 5 | ---@internal 6 | ---@deprecated 7 | ---@name melon.DefineNewBehavior 8 | ---- 9 | ---- Weird experiment thats not really used, dont use. 10 | ---- 11 | function melon.DefineNewBehavior(key, fn) 12 | behavior[key] = fn 13 | end 14 | 15 | -- setmetatable(melon, { 16 | -- __index = function(s, k) 17 | -- if behavior[k] then 18 | -- return behavior[k]() 19 | -- end 20 | 21 | -- return rawget(s, k) 22 | -- end 23 | -- }) -------------------------------------------------------------------------------- /lua/melon/preload/sh_logging.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@member 4 | ---@name melon.LogTypes 5 | ---- 6 | ---- List of all log types 7 | ---- 8 | melon.LogTypes = melon.LogTypes or {} 9 | 10 | local logs = {} 11 | local logtypes = melon.LogTypes 12 | 13 | ---- 14 | ---@name melon.AddLogHandler 15 | ---- 16 | ---@arg (level: number) Number, log level 17 | ---@arg (log: func(table)) Function to call when logging, called with logdata 18 | ---- 19 | ---- Adds a log handler 20 | ---- 21 | function melon.AddLogHandler(lvl, func) 22 | logtypes[lvl] = func 23 | end 24 | 25 | ---- 26 | ---@name melon.AddDynamicLogHandler 27 | ---- 28 | ---@arg (log: func(table)) Function to call when logging, called with logdata 29 | ---@return (type: number) Log Level 30 | ---- 31 | ---- Adds a log handler which just appends to the previous logtypes, returns the level of this handler 32 | ---- 33 | function melon.AddDynamicLogHandler(func) 34 | local num = #logtypes + 1 35 | 36 | logtypes[num] = func 37 | 38 | return num 39 | end 40 | 41 | ---- 42 | ---@name melon.Log 43 | ---- 44 | ---@arg (level: number) The log level 45 | ---@arg (fmt: string) String format to log 46 | ---@arg (fmtargs: ...any) Arguments for the formatter 47 | ---- 48 | ---- Formats and logs a message, also calls the hook `Melon:Log` with the logdata 49 | ---- 50 | function melon.Log(lvl, fmt, ...) 51 | local vg = {...} 52 | 53 | local logMessage 54 | if melon.string and melon.string.Format then 55 | logMessage = melon.string.Format(fmt, vg) 56 | else 57 | logMessage = string.gsub(fmt, "{%d+}", function(x) 58 | return ((vg)[tonumber(x:sub(2, -2))]) or x 59 | end ) 60 | end 61 | 62 | local time = os.time() 63 | local fmt_time = string.FormattedTime(CurTime(), math.floor(CurTime() / 3600) .. ":%02i:%02i:%02i") 64 | 65 | local l = { 66 | message = logMessage, 67 | trace = debug.traceback("", 2), 68 | time = time, 69 | level = lvl, 70 | handler = logtypes[lvl], 71 | fmt_time = fmt_time 72 | } 73 | table.insert(logs, l) 74 | 75 | logtypes[lvl](l) 76 | hook.Run("Melon:Log", l) 77 | end 78 | 79 | ---- 80 | ---@name melon.Assert 81 | ---- 82 | ---@arg (expr: bool) Did this expression pass? Must be true to pass 83 | ---@arg (fmt: string) Format string error if we failed 84 | ---@arg (args: ...any) Arguments for the formatted 85 | ---- 86 | ---- Asserts the expression, returns true if you should return 87 | ---- 88 | function melon.Assert(expr, fmt, ...) 89 | if expr == true then -- dont accept truey expressions as valid 90 | return false 91 | end 92 | 93 | melon.Log(1, fmt, ...) 94 | return true 95 | end 96 | 97 | concommand.Add("melon_dump_logs", function() 98 | local str = "" 99 | 100 | for k,v in pairs(logs) do 101 | str = str .. 102 | "\n[" .. v.time .. "](" .. v.level .. ") " .. v.message 103 | str = str .. 104 | "\n[trace] " .. v.trace .. "\n" 105 | end 106 | 107 | file.Write("melon_lib_logs.txt", str) 108 | print(str) 109 | 110 | melon.Log(3, "Wrote logs to file!") 111 | end) 112 | 113 | hook.Add("ShutDown", "Melon:Logs:DumpingToFile", function() 114 | RunConsoleCommand("melon_dump_logs") 115 | end ) 116 | 117 | -- Handler Definitions 118 | local colors = { 119 | white = color_white, 120 | light_blue = Color(38, 248, 255), 121 | orange = Color(255,255,0), 122 | red = Color(255,0,0), 123 | green = Color(100, 255, 100) 124 | } 125 | 126 | -- Verbose Stuff, Basically useless nonsense 127 | melon.AddLogHandler(3, function(msg) 128 | MsgC(colors.light_blue, "[MelonLib (", msg.fmt_time , ")][Message] ", colors.white, msg.message, "\n") 129 | end) 130 | 131 | -- Warnings 132 | melon.AddLogHandler(2, function(msg) 133 | MsgC(colors.orange, "[MelonLib (", msg.fmt_time , ")][Warn] ", colors.white, msg.message, "\n") 134 | end) 135 | 136 | -- Errors 137 | melon.AddLogHandler(1, function(msg) 138 | if CLIENT then 139 | ErrorNoHaltWithStack("[MelonLib][Error] " .. msg.message) 140 | return 141 | end 142 | 143 | MsgC(colors.red, "[MelonLib (", msg.fmt_time , ")][Error] ", colors.white, msg.message, "\n") 144 | end) 145 | 146 | -- Mandatory, Non-error messages 147 | melon.AddLogHandler(0, function(msg) 148 | MsgC(colors.green, "[MelonLib (", msg.fmt_time , ")][Important] ", colors.white, msg.message, "\n") 149 | end ) 150 | 151 | ---- 152 | ---@enumeration 153 | ---@name melon.LOG 154 | ---- 155 | ---@enum (IMPORTANT) Important, green text 156 | ---@enum (ERROR) Error, red text or an actual error on the client 157 | ---@enum (WARNING) A warning 158 | ---@enum (MESSAGE) A verbose message 159 | ---- 160 | ---- Log types 161 | ---- 162 | melon.LOG_IMPORTANT = 0 163 | melon.LOG_ERROR = 1 164 | melon.LOG_WARNING = 2 165 | melon.LOG_MESSAGE = 3 -------------------------------------------------------------------------------- /lua/melon/preload/sh_postload.lua: -------------------------------------------------------------------------------- 1 | 2 | local runPostLoad = melon.FinishedLoading or {} 3 | 4 | ---- 5 | ---@name melon.PostLoad 6 | ---- 7 | ---@arg (fn: func) Function to call post load 8 | ---- 9 | ---- Call a function post load, needed because of load order issues 10 | ---- 11 | function melon.PostLoad(fn) 12 | if melon.FinishedLoading then 13 | return fn() 14 | end 15 | 16 | if istable(runPostLoad) then 17 | return table.insert(runPostLoad, fn) 18 | end 19 | 20 | fn() 21 | end 22 | 23 | hook.Add("Melon:DoneLoading", "melon.PostLoad", function() 24 | if not istable(runPostLoad) then return end 25 | 26 | for _, fn in pairs(runPostLoad) do 27 | fn() 28 | end 29 | 30 | runPostLoad = nil 31 | end ) 32 | -------------------------------------------------------------------------------- /lua/melon/preload/sh_profile.lua: -------------------------------------------------------------------------------- 1 | 2 | ---- 3 | ---@deprecated 4 | ---@name melon.Profile 5 | ---- 6 | ---- Unsure how functional this actually is. 7 | ---- 8 | function melon.Profile(func, name, stop_profile) 9 | if stop_profile then 10 | return func 11 | end 12 | 13 | return function(...) 14 | local start = SysTime() 15 | local ret = func(...) 16 | local endd = SysTime() 17 | melon.Log(0, "Finished Profiling Function '{1}', ran in {2}s", name or "unknown", endd - start) 18 | return ret 19 | end 20 | end -------------------------------------------------------------------------------- /lua/melon/preload/sh_types.lua: -------------------------------------------------------------------------------- 1 | 2 | local isn = isnumber 3 | 4 | ---- 5 | ---@name melon.IsColor 6 | ---- 7 | ---@arg (color: table) Color to check 8 | ---@return (iscol: bool) Is the given table a color? 9 | ---- 10 | ---- Check if the given value is a color, use istable first. 11 | ---- 12 | function melon.IsColor(col) 13 | return IsColor(col) or (istable(col) and (isn(col.r) and isn(col.g) and isn(col.b))) 14 | end 15 | 16 | local _r = debug.getregistry() 17 | 18 | ---- 19 | ---@name melon.ToColor 20 | ---- 21 | ---@arg (input: table) Table to convert to a Color 22 | ---@return (color: color) New Color object 23 | ---- 24 | ---- Converts the given table into a valid [Color] object 25 | ---- 26 | function melon.ToColor(tbl) 27 | tbl.r = tbl.r or 0 28 | tbl.g = tbl.g or 0 29 | tbl.b = tbl.b or 0 30 | tbl.a = tbl.a or 255 31 | 32 | return setmetatable(table.Copy(tbl), _r.Color) 33 | end 34 | -------------------------------------------------------------------------------- /resource/fonts/poppins_melon_lib.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/melonstuff/melonlib/d86cb7a4bd3edd770c05638b92e96fc02e893224/resource/fonts/poppins_melon_lib.ttf --------------------------------------------------------------------------------