From 7e2b0678257b285cadf01da0c62bc892433b0dbb Mon Sep 17 00:00:00 2001 From: Michael Werle Date: Sun, 10 Nov 2024 19:54:57 +0900 Subject: [PATCH] feat(radar): major cleanup refactor This commit is a major refactor of the radar module code. The scanner and radar were each turned into a class (table) to make the main draw function more generic. TODO: there's probably a good way to refactor this further to have both instruments derive from a base class in order to ensure the same base functionality. Additionally: * The 'onChangeMFD' event was removed * The scanner (3D) and radar (2D azimuth) now each have their own zoom level. This prevents the radar from ending up with an auto-zoom zoom level when switching from the scanner since it only ever has manual zoom. * The autopilot controls were also moved to the right a little bit to make more room for the zoom-level display. * The scanner was made marginally taller. --- data/pigui/modules/autopilot-window.lua | 3 +- data/pigui/modules/radar.lua | 299 +++++++++++++----------- 2 files changed, 165 insertions(+), 137 deletions(-) diff --git a/data/pigui/modules/autopilot-window.lua b/data/pigui/modules/autopilot-window.lua index f2e8f44576..753a138389 100644 --- a/data/pigui/modules/autopilot-window.lua +++ b/data/pigui/modules/autopilot-window.lua @@ -252,7 +252,8 @@ local function displayAutoPilotWindow() local current_view = Game.CurrentView() local window_h = mainButtonSize.y + smallButtonSize.y + ui.getWindowPadding().y * 2 local shift = smallButtonSize.y - local window_posx = ui.screenWidth/2 + ui.reticuleCircleRadius / 4 * 3 + -- X starting position is the edge of the scanner display. + local window_posx = ui.screenWidth/2 + ui.reticuleCircleRadius local window_posy = ui.screenHeight - window_h ui.setNextWindowPos(Vector2(window_posx, window_posy) , "Always") ui.window("AutoPilot", {"NoTitleBar", "NoResize", "NoFocusOnAppearing", "NoBringToFrontOnFocus", "NoSavedSettings", "AlwaysAutoResize"}, diff --git a/data/pigui/modules/radar.lua b/data/pigui/modules/radar.lua index 9ed7026597..5bd4bdfee8 100644 --- a/data/pigui/modules/radar.lua +++ b/data/pigui/modules/radar.lua @@ -1,13 +1,10 @@ -- Copyright © 2008-2024 Pioneer Developers. See AUTHORS.txt for details -- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt -local Engine = require 'Engine' local Game = require 'Game' -local utils = require 'utils' local Event = require 'Event' local Lang = require 'Lang' -local lc = Lang.GetResource("core"); local lui = Lang.GetResource("ui-core"); local ui = require 'pigui' @@ -17,13 +14,13 @@ local pionillium = ui.fonts.pionillium local colors = ui.theme.colors local icons = ui.theme.icons +local SCREEN_BORDER = 4 + local MAX_RADAR_SIZE = 1000000000 local MIN_RADAR_SIZE = 1000 local DEFAULT_RADAR_SIZE = 10000 -local shouldDisplay2DRadar -local current_radar_size = DEFAULT_RADAR_SIZE -local manual_zoom = false +local shouldDisplay2DRadar = false local blobSize = 6.0 local function getColorFor(item) @@ -46,29 +43,78 @@ local function getColorFor(item) return colors.radarUnknown end +local radar2d = { + zoom = DEFAULT_RADAR_SIZE, + size = ui.reticuleCircleRadius * 0.66, + getRadius = function(self) return self.size end, + zoomIn = function(self) + self.zoom = math.max(self.zoom / 10, MIN_RADAR_SIZE) + end, + zoomOut = function(self) + self.zoom = math.min(self.zoom * 10, MAX_RADAR_SIZE) + end, + resetZoom = function(self) + self.zoom = DEFAULT_RADAR_SIZE + end, + getZoom = function(self) return self.zoom end, + isAutoZoom = function(self) return false end, +} + +local radar = require 'PiGui.Modules.RadarWidget'() +radar.minZoom = MIN_RADAR_SIZE +radar.maxZoom = MAX_RADAR_SIZE +radar.zoom = DEFAULT_RADAR_SIZE +local radar3d = { + auto_zoom = true, + size = Vector2(ui.reticuleCircleRadius * 1.8, ui.reticuleCircleRadius * 1.4), + getRadius = function(self) return self.size.x end, + zoomIn = function(self) + if self.auto_zoom then + radar.zoom = 10 ^ math.floor(math.log(radar.zoom, 10)) + else + radar.zoom = math.max(radar.zoom / 10, MIN_RADAR_SIZE) + end + self.auto_zoom = false + end, + zoomOut = function(self) + if self.auto_zoom then + radar.zoom = 10 ^ math.ceil(math.log(radar.zoom, 10)) + else + radar.zoom = math.min(radar.zoom * 10, MAX_RADAR_SIZE) + end + self.auto_zoom = false + end, + resetZoom = function(self) + radar.zoom = DEFAULT_RADAR_SIZE + self.auto_zoom = true + end, + getZoom = function(self) return radar.zoom end, + isAutoZoom = function(self) return self.auto_zoom end, +} + -- display the 2D radar -local function display2DRadar(cntr, size) - local targets = ui.getTargetsNearby(current_radar_size) - local halfsize = size * 0.5 - local thirdsize = size * 0.3 - local twothirdsize = size * 0.7 +radar2d.draw = function(self, center) + local targets = ui.getTargetsNearby(self.zoom) + local halfsize = self.size * 0.5 + local thirdsize = self.size * 0.3 + local twothirdsize = self.size * 0.7 local fgColor = colors.uiPrimary local bgColor = colors.uiBackground:opacity(0.54) local lineThickness = 1.5 local function line(x,y) - -- ui.addLine(cntr + Vector2(x, y) * halfsize, cntr + Vector2(x,y) * size, colors.uiPrimaryDark, lineThickness) - ui.addLine(cntr + Vector2(x, y) * thirdsize, cntr + Vector2(x,y) * twothirdsize, fgColor, lineThickness) + -- ui.addLine(center + Vector2(x, y) * halfsize, center + Vector2(x,y) * size, colors.uiPrimaryDark, lineThickness) + ui.addLine(center + Vector2(x, y) * thirdsize, center + Vector2(x,y) * twothirdsize, fgColor, lineThickness) end -- radar background and border - ui.addCircleFilled(cntr, size, bgColor, ui.circleSegments(size), 1) - ui.addCircle(cntr, size, fgColor, ui.circleSegments(size), lineThickness * 2) + ui.addCircleFilled(center, self.size, bgColor, ui.circleSegments(self.size), 1) + ui.addCircle(center, self.size, fgColor, ui.circleSegments(self.size), lineThickness * 2) -- inner circles - -- ui.addCircle(cntr, halfsize, fgColor, ui.circleSegments(halfsize), lineThickness) - ui.addCircle(cntr, thirdsize, fgColor, ui.circleSegments(thirdsize), lineThickness) - ui.addCircle(cntr, twothirdsize, fgColor, ui.circleSegments(twothirdsize), lineThickness) + -- ui.addCircle(center, halfsize, fgColor, ui.circleSegments(halfsize), lineThickness) + ui.addCircle(center, thirdsize, fgColor, ui.circleSegments(thirdsize), lineThickness) + ui.addCircle(center, twothirdsize, fgColor, ui.circleSegments(twothirdsize), lineThickness) local l = ui.oneOverSqrtTwo -- cross-lines line(-l, l) @@ -81,13 +127,13 @@ local function display2DRadar(cntr, size) local navTarget = player:GetNavTarget() local tooltip = {} for k,v in pairs(targets) do - if v.distance < current_radar_size then - local halfRadarSize = current_radar_size / 2 + if v.distance < self.zoom then + local halfRadarSize = self.zoom / 2 local alpha = 255 if v.distance > halfRadarSize then alpha = 255 * (1 - (v.distance - halfRadarSize) / halfRadarSize) end - local position = cntr + v.aep * size * 2 + local position = center + v.aep * self.size * 2 if v.body == navTarget then local color = Color(colors.navTarget.r, colors.navTarget.g, colors.navTarget.b, alpha) ui.addIcon(position, icons.square, color, Vector2(12, 12), ui.anchor.center, ui.anchor.center) @@ -107,9 +153,6 @@ local function display2DRadar(cntr, size) if #tooltip > 0 then ui.setTooltip(table.concat(tooltip, "\n")) end - -- local distance = ui.Format.Distance(current_radar_size) - -- local textcenter = cntr + Vector2((halfsize + twothirdsize) * 0.5, size) - -- local textsize = ui.addStyledText(textcenter, ui.anchor.left, ui.anchor.bottom, distance, colors.frame, pionillium.small, lui.HUD_RADAR_DISTANCE, colors.lightBlackBackground) end -- Return tooltip for target @@ -129,12 +172,7 @@ local function drawTarget(target, scale, center, color) return tooltip end -local radar = require 'PiGui.Modules.RadarWidget'() ---local currentZoomDist = MIN_RADAR_SIZE -radar.minZoom = MIN_RADAR_SIZE -radar.maxZoom = MAX_RADAR_SIZE - -local function display3DRadar(center, size) +radar3d.draw = function(self, center) local targets = ui.getTargetsNearby(MAX_RADAR_SIZE) local tooltip = {} @@ -144,176 +182,165 @@ local function display3DRadar(center, size) local maxShipDist = 0.0 local maxCargoDist = 0.0 - radar.size = size - radar.zoom = current_radar_size or DEFAULT_RADAR_SIZE - local radius = radar.radius + radar.size = self.size + radar.zoom = radar.zoom or DEFAULT_RADAR_SIZE local scale = radar.radius / radar.zoom - ui.setCursorPos(center - size / 2.0) + ui.setCursorPos(center - self.size / 2.0) -- draw targets below the plane for k, v in pairs(targets) do -- collect some values for zoom updates later maxBodyDist = math.max(maxBodyDist, v.distance) -- only snap to ships if they're less than 50km away (arbitrary constant based on crime range) - if v.body:IsShip() and v.distance < 50000 then maxShipDist = math.max(maxShipDist, v.distance) end + if v.body:IsShip() and v.distance < 50000 then + maxShipDist = math.max(maxShipDist, v.distance) + end -- only snap to cargo containers if they're less than 25km away (arbitrary) - if v.body:IsCargoContainer() and v.distance < 25000 then maxCargoDist = math.max(maxCargoDist, v.distance) end + if v.body:IsCargoContainer() and v.distance < 25000 then + maxCargoDist = math.max(maxCargoDist, v.distance) + end - if v.distance < current_radar_size and v.rel_position.y < 0.0 then - local color = (v.body == navTarget and colors.navTarget) or (v.body == combatTarget and colors.combatTarget) or getColorFor(v) + if v.distance < radar.zoom and v.rel_position.y < 0.0 then + local color = (v.body == navTarget and colors.navTarget) or + (v.body == combatTarget and colors.combatTarget) or + getColorFor(v) table.append(tooltip, drawTarget(v, scale, center, color)) end end + -- draw the radar plane itself ui.withStyleColors({ FrameBg = colors.radarBackground, FrameBgActive = colors.radarFrame, }, function() - -- draw the radar plane itself radar:Draw() end) - -- draw targets above the plane for k, v in pairs(targets) do - if v.distance < current_radar_size and v.rel_position.y >= 0.0 then - local color = (v.body == navTarget and colors.navTarget) or (v.body == combatTarget and colors.combatTarget) or getColorFor(v) + if v.distance < radar.zoom and v.rel_position.y >= 0.0 then + local color = (v.body == navTarget and colors.navTarget) or + (v.body == combatTarget and colors.combatTarget) or + getColorFor(v) table.append(tooltip, drawTarget(v, scale, center, color)) end end + -- return tooltip if mouse is over a target if #tooltip > 0 then ui.setTooltip(table.concat(tooltip, "\n")) end -- handle automatic radar zoom based on player surroundings - if not manual_zoom then + if self.auto_zoom then local maxDist = maxBodyDist if combatTarget then maxDist = combatTarget:GetPositionRelTo(player):length() * 1.4 elseif maxShipDist > 0 then maxDist = maxShipDist * 1.4 elseif maxCargoDist > 0 then - maxDist = maxCargoDist * 1.4 + maxDist = maxCargoDist * 1.4 elseif navTarget then local dist = navTarget:GetPositionRelTo(player):length() maxDist = dist > MAX_RADAR_SIZE and maxBodyDist or dist * 1.4 end - current_radar_size = math.clamp(radar.zoom + (maxDist - radar.zoom) * 0.03, - MIN_RADAR_SIZE, MAX_RADAR_SIZE) + radar.zoom = math.clamp(radar.zoom + (maxDist - radar.zoom) * 0.03, MIN_RADAR_SIZE, MAX_RADAR_SIZE) end - - -- local distance = ui.Format.Distance(current_radar_size) - -- local textwidth = ui.calcTextSize(distance).x - -- local textpos = center + Vector2(textwidth / -2, size.y * 0.42) - -- local textsize = ui.addStyledText(textpos, ui.anchor.left, ui.anchor.bottom, distance, colors.frame, pionillium.small, lui.HUD_RADAR_DISTANCE, colors.lightBlackBackground) end +-- This variable needs to be outside the function in order to capture state +-- between frames. We are trying to ensure that a mouse right-click started and +-- finished inside the radar area in order to trigger the popup. local click_on_radar = false + -- display either the 3D or the 2D radar, show a popup on right click to select local function displayRadar() if ui.optionsWindow.isOpen or Game.CurrentView() ~= "world" then return end - player = Game.player + player = player or Game.player -- only display if there actually *is* a radar installed local equipped_radar = player:GetComponent("EquipSet"):GetInstalledOfType("sensor.radar") - if #equipped_radar > 0 then - - local size = ui.reticuleCircleRadius * 0.66 - local center = Vector2(ui.screenWidth / 2, ui.screenHeight - size - 4) - local zoom = 0 - local toggle_radar = false - - -- Handle keyboard - -- TODO: figure out how to map the keys instead of hard-coding them - if ui.isKeyReleased(ui.keys.left) then - zoom = 1 -- zoom in - elseif ui.isKeyReleased(ui.keys.right) then - zoom = -1 -- zoom out - end - if ui.isKeyReleased(ui.keys.up) then - zoom = 0 - manual_zoom = false - current_radar_size = DEFAULT_RADAR_SIZE - end - if ui.isKeyReleased(ui.keys.down) then - toggle_radar = true - end + -- TODO: get ship radar capability and determine functionality based on level of radar installed + if #equipped_radar == 0 then return end + + local instrument = shouldDisplay2DRadar and radar2d or radar3d + local center = Vector2(ui.screenWidth / 2, ui.screenHeight - radar2d.size - SCREEN_BORDER) + local zoom = 0 + local toggle_radar = false + + -- Handle keyboard + -- TODO: figure out how to map the keys instead of hard-coding them + if ui.isKeyReleased(ui.keys.left) then + zoom = 1 -- zoom in + elseif ui.isKeyReleased(ui.keys.right) then + zoom = -1 -- zoom out + end + if ui.isKeyReleased(ui.keys.up) then + zoom = 0 + instrument:resetZoom() + end + if ui.isKeyReleased(ui.keys.down) then + toggle_radar = true + end - -- Handle mouse if it is in the radar area - local mp = ui.getMousePos() - local mouse_dist = shouldDisplay2DRadar and size or size * 1.8 - if (mp - center):length() > mouse_dist then - click_on_radar = false - end - if (mp - center):length() < mouse_dist then - if ui.isMouseClicked(1) then - click_on_radar = true - end - if not toggle_radar and click_on_radar and ui.isMouseReleased(1) then - ui.openPopup("radarselector") - end - -- TODO: figure out how to "capture" the mouse wheel to prevent - -- the game engine from using it to also zoom the viewport - if zoom == 0 then - zoom = ui.getMouseWheel() - end - end - if zoom > 0 then - -- Zoom in (decrease scanned area) - if not manual_zoom then - current_radar_size = 10 ^ math.floor(math.log(current_radar_size, 10)) - else - current_radar_size = math.max(current_radar_size / 10, MIN_RADAR_SIZE) - end - manual_zoom = true - elseif zoom < 0 then - -- Zoom out (increase scanned area) - if not manual_zoom then - current_radar_size = 10 ^ math.ceil(math.log(current_radar_size, 10)) - else - current_radar_size = math.min(current_radar_size * 10, MAX_RADAR_SIZE) - end - manual_zoom = true - end + -- Handle mouse if it is in the radar area + local mp = ui.getMousePos() + if (mp - center):length() < radar2d.getRadius(radar2d) then ui.popup("radarselector", function() if ui.selectable(lui.HUD_2D_RADAR, shouldDisplay2DRadar, {}) then - Event.Queue('onChangeMFD', 'radar') + toggle_radar = true end if ui.selectable(lui.HUD_3D_RADAR, not shouldDisplay2DRadar, {}) then - Event.Queue('onChangeMFD', 'scanner') + toggle_radar = true end end) - -- Draw the actual radar - if shouldDisplay2DRadar then - display2DRadar(center, size) - else - display3DRadar(center, Vector2(ui.reticuleCircleRadius * 1.8, size * 2)) + if ui.isMouseClicked(1) then + click_on_radar = true end - -- Draw the range indicator - local distance = ui.Format.Distance(current_radar_size) - local textpos = Vector2(center.x + size, center.y + size) - local textsize = ui.addStyledText(textpos, ui.anchor.right, ui.anchor.bottom, distance, colors.frame, pionillium.small, lui.HUD_RADAR_DISTANCE, colors.lightBlackBackground) - -- Draw the radar mode in bottom-left corner - -- TODO: use an icon - local mode = manual_zoom and '[M]' or '[A]' - -- TODO: have button sized relative to radar size - -- Also, button currently does not react to mouse click nor shows tooltip on hover - local button_size = size / 3.5 -- 25 - ui.setCursorPos(Vector2(center.x - size - button_size, center.y + size - button_size)) - local clicked = ui.button(mode, Vector2(button_size), ui.theme.buttonColors.default, "Does the thing") - if toggle_radar or clicked then - shouldDisplay2DRadar = not shouldDisplay2DRadar - Event.Queue('onChangeMFD', shouldDisplay2DRadar and 'radar' or 'scanner') + if not toggle_radar and click_on_radar and ui.isMouseReleased(1) then + ui.openPopup("radarselector") + end + -- TODO: figure out how to "capture" the mouse wheel to prevent + -- the game engine from using it to also zoom the viewport + if zoom == 0 then + zoom = ui.getMouseWheel() end end -end -Event.Register('onChangeMFD', function(selected) - shouldDisplay2DRadar = selected == "radar"; -end) + -- Update the zoom level + if zoom > 0 then + instrument:zoomIn() + elseif zoom < 0 then + instrument:zoomOut() + end + + -- Draw the actual radar + instrument:draw(center) + + -- Draw the range indicator - base the offsets and sizes on the 2D radar + local size = radar2d:getRadius() + local distance = ui.Format.Distance(instrument:getZoom()) + local textpos = Vector2(center.x + ui.reticuleCircleRadius * 0.9, center.y + size - SCREEN_BORDER) + local textsize = ui.addStyledText(textpos, ui.anchor.right, ui.anchor.bottom, distance, colors.frame, pionillium.small, lui.HUD_RADAR_DISTANCE, colors.lightBlackBackground) + -- Draw the radar mode button in bottom-left corner + -- Button currently does not react to mouse click nor shows tooltip on hover - WHY?? + local button_size = size / 3.5 -- 25 + ui.setCursorPos(Vector2(center.x - size - button_size, center.y + size - button_size - SCREEN_BORDER)) + local clicked = ui.mainMenuButton(icons.equip_radar, "Toggle scanner mode", false, Vector2(button_size)) + if toggle_radar or clicked then + shouldDisplay2DRadar = not shouldDisplay2DRadar + end + -- Draw zoom mode indicator + if not shouldDisplay2DRadar then + -- local mode = manual_zoom and '[M]' or '[A]' + local mode = instrument:isAutoZoom() and 'A' or 'M' + button_size = button_size / 1.5 + ui.setCursorPos(Vector2(center.x - size + 4, center.y + size - button_size - SCREEN_BORDER)) + ui.button(mode, Vector2(button_size), false, "Zoom Mode") + end +end -- reset radar to default at game end Event.Register("onGameEnd", function() shouldDisplay2DRadar = false end)