From c8a4d63fda2e428f231ecc19f1ab66b29641fe8b Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Sat, 12 Jan 2019 20:26:41 +0100 Subject: [PATCH] uimanager: Basic UI Manager for DPI and similar --- .../gamemode/client/uimanager.lua | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 source/gamemodes/prophuntextended/gamemode/client/uimanager.lua diff --git a/source/gamemodes/prophuntextended/gamemode/client/uimanager.lua b/source/gamemodes/prophuntextended/gamemode/client/uimanager.lua new file mode 100644 index 0000000..e478d56 --- /dev/null +++ b/source/gamemodes/prophuntextended/gamemode/client/uimanager.lua @@ -0,0 +1,187 @@ +--[[ + The MIT License (MIT) + + Copyright (c) 2015-2018 Xaymar + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +--]] + +--[[ + +# Class UIManager +UIManager is used to simplify the logic required to keep the GameMode's UI looking + good by simply moving all the logic into a single class and then handing off + the UI control to the windows themselves. + +# Events +Events must be explicitly listened to by the client object. A client must first call + UIManager:Listen(string event, any uniqueId, function callback) +to listen to an event and call + UIManager:Silence(string event, any uniqueId) +to stop listening to an event. + +## UpdateDPI(float DPIPercent) +Called whenever a change is made to the DPI, such as a change in the screen resolution, + or an update from the user of this class. Only parameter is DPI, which is a float. +1.0 designates 100%, 2.0 designates 200% and so on. + +--]] + +local UIManagerMeta = {} +UIManagerMeta.__index = UIManagerMeta + +-- Global Constructor +function UIManager() + local obj = {} + obj.__index = obj + setmetatable(obj, UIManagerMeta) + obj:__construct() + return obj +end + +-- Constructor +function UIManagerMeta:__construct() + self:InitializeEvents() + self:UpdateScreen() + self:InitializeDPI() +end + +-- Events +function UIManagerMeta:InitializeEvents() + self.events = {} +end + +function UIManagerMeta:Listen(event, uniqueName, callback) + assert(type(event) == "string", "UIManager:Listen(event, callback): event must be a string") + assert(type(callback) == "function", "UIManager:Listen(event, callback): callback must be a function") + + if (self.events[event] == nil) then + self.events[event] = {} + end + + self.events[event][uniqueName] = callback + return true +end + +function UIManagerMeta:Silence(event, uniqueName) + if (self.events[event] == nil) then + return false + end + + if (self.events[event][uniqueName] == nil) then + return false + end + + self.events[event][uniqueName] = nil + return true +end + +function UIManagerMeta:Call(event, ...) + if (self.events[event] == nil) then + return + end + + for k,v in pairs(self.events[event]) do + print(k) + print(v) + v(...) + end +end + +-- Screen +function UIManagerMeta:UpdateScreen() + if (self.screen == nil) then + self.screen = {} + end + if ((self.screen.width != ScrW()) || (self.screen.height != ScrH())) then + self.screen.width = ScrW() + self.screen.height = ScrH() + return true + end + return false +end + +-- Tick +function UIManagerMeta:Tick() + if (self:UpdateScreen() == true) then + self.dpi.calculated = self:CalculateDPI() + if (self.dpi.override == nil) then + self:Call("UpdateDPI", self.dpi.calculated) + else + self:Call("UpdateDPI", self.dpi.override) + end + end +end + +-- DPI Scaling +function UIManagerMeta:InitializeDPI() + self.dpi = {} + -- Calculate DPI using these base sizes. + self.dpi.base = {} + self.dpi.base.width = 1280 + self.dpi.base.height = 720 + -- Limits + self.dpi.limits = {} + self.dpi.limits.min = 25 + self.dpi.limits.max = 400 + -- Final Value + self.dpi.override = nil + self.dpi.calculated = self:CalculateDPI() +end + +function UIManagerMeta:CalculateDPI() + --[[ + DPI Scaling is linear in respect to the base resolution. + That means that at 50% of the Base resolution, the output is 0.5, + and at 200% of the Base resolution, the output is 2.0. + --]] + + -- Shortest Edge scaling + local wScale, hScale = self.screen.width / self.dpi.base.width, self.screen.height / self.dpi.base.height + + if (wScale < hScale) then + return wScale + end + return hScale +end + +function UIManagerMeta:GetDPIScale() + if (self.dpi.override != nil) then + return self.dpi.override + end + return self.dpi.calculated +end + +function UIManagerMeta:SetDPIScale(newDPIScale) + if (newDPIScale == nil) then + self.dpi.override = nil + self:Call("UpdateDPI", self.dpi.calculated) + else + self.dpi.override = newDPIScale + if (self.dpi.override < self.dpi.limits.min) then + self.dpi.override = self.dpi.limits.min + end + if (self.dpi.override > self.dpi.limits.max) then + self.dpi.override = self.dpi.limits.max + end + self:Call("UpdateDPI", self.dpi.override) + end +end + +