From fcf3e0f7bbf257944e611d3bdf22c384c7606e9c Mon Sep 17 00:00:00 2001 From: Michael Werle Date: Thu, 7 Nov 2024 09:09:54 +0900 Subject: [PATCH] feat(radar): refactor radar code and improve UI layout 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. The layout was also modified significantly with both the left and right button bars moved further away for an overall less cluttered look. This made room for a couple of new buttons, one to switch between the scanner and the radar, and the other to indicate the current scanner zoom mode - automatic or manual. In order to get the new buttons near the radar to actually work, they had to be rendered inside a window as otherwise they are completely unresponsive the mouse. HOWEVER, placing the entire randar into the window does not work as the 3D radar's background is not rendered when it's in the window (possibly due to coordinate issues? Alternatively because it's rendered in C++ instead of LUA?) although its "pips" are. The 2D radar is rendered properly even when inside a window. 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 ship-internals window is moved to the left a bit to avoid it encroaching on the radar area. * The autopilot controls are moved to the right a little bit to make more room for the zoom-level display. * The new buttons for the radar are aligned to the left of the radar area. * The scanner was made marginally taller. * The zoom levels are reset for both the scanner and the radar at game-end. * Add a debugReload() function to the ship-internals window. --- data/lang/ui-core/en.json | 12 + data/pigui/modules/autopilot-window.lua | 3 +- data/pigui/modules/radar.lua | 320 +++++++++++-------- data/pigui/modules/ship-internals-window.lua | 10 +- 4 files changed, 207 insertions(+), 138 deletions(-) diff --git a/data/lang/ui-core/en.json b/data/lang/ui-core/en.json index 9847762efe..f1c1a79589 100644 --- a/data/lang/ui-core/en.json +++ b/data/lang/ui-core/en.json @@ -1159,6 +1159,18 @@ "description": "Tooltip: Current distance of the radar.", "message": "Active radar distance" }, + "HUD_RADAR_ZOOM_MODE_AUTOMATIC": { + "description": "Tooltip: Current radar zoom mode.", + "message": "Automatic zoom" + }, + "HUD_RADAR_ZOOM_MODE_MANUAL": { + "description": "Tooltip: Current radar zoom mode.", + "message": "Manual zoom" + }, + "HUD_RADAR_TOGGLE_MODE": { + "description": "Tooltip: Toggle radar mode.", + "message": "Toggle radar between 2D and 3D modes" + }, "HUD_REQUEST_TIME_ACCEL": { "description": "Tooltip: Request time acceleration {time}", "message": "Request time acceleration: {time}" diff --git a/data/pigui/modules/autopilot-window.lua b/data/pigui/modules/autopilot-window.lua index 4c6c49384b..9d99cd1cad 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 eead59e9e4..a6f9ef965b 100644 --- a/data/pigui/modules/radar.lua +++ b/data/pigui/modules/radar.lua @@ -1,14 +1,11 @@ -- 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 Input = require 'Input' local Lang = require 'Lang' -local lc = Lang.GetResource("core"); local lui = Lang.GetResource("ui-core"); local ui = require 'pigui' @@ -18,13 +15,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 keys = { @@ -55,31 +52,82 @@ local function getColorFor(item) return colors.radarUnknown end +local radar2d = { + icon = icons.radar_2d, + 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 = { + icon = icons.radar_3d, + 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) -- Uncomment to extend the radial line all the way to the outer circle - -- ui.addLine(cntr + Vector2(x, y) * halfsize, center + 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 -- Uncomment to add an additional circle dividing the 2D radar display - -- 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) @@ -92,13 +140,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) @@ -118,9 +166,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 @@ -140,12 +185,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 = {} @@ -155,176 +195,187 @@ 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: Convert to axis? - if keys.radar_zoom_in:IsJustActive() then - zoom = 1 - elseif keys.radar_zoom_out:IsJustActive() then - zoom = -1 - end - if keys.radar_reset:IsJustActive() then - zoom = 0 - manual_zoom = false - current_radar_size = DEFAULT_RADAR_SIZE - end - if keys.radar_toggle_mode:IsJustActive() 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: Convert to axis? + if keys.radar_zoom_in:IsJustActive() then + zoom = 1 + elseif keys.radar_zoom_out:IsJustActive() then + zoom = -1 + end + if keys.radar_reset:IsJustActive() then + zoom = 0 + instrument:resetZoom() + end + if keys.radar_toggle_mode:IsJustActive() 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() + -- TODO: adjust properly for 3D radar; bit more challenging as its an ellipse + 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) - if toggle_radar then - shouldDisplay2DRadar = not shouldDisplay2DRadar - Event.Queue('onChangeMFD', shouldDisplay2DRadar and 'radar' or 'scanner') + if ui.isMouseClicked(1) then + click_on_radar = true end - -- Draw the actual radar - if shouldDisplay2DRadar then - display2DRadar(center, size) - else - display3DRadar(center, Vector2(ui.reticuleCircleRadius * 1.8, size * 2)) + 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 - -- 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]' - textpos = Vector2(center.x - size, center.y + size) - ui.addStyledText(textpos, ui.anchor.left, ui.anchor.bottom, mode, colors.alertRed, pionillium.small, lui.HUD_RADAR_DISTANCE, colors.lightBlackBackground) 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 - this can't be in a window or the 3D scanner background doesn't render + instrument:draw(center) + + -- Draw the radar buttons and info - this needs to be in a window, otherwise the buttons don't work. + local window_width = ui.reticuleCircleRadius * 1.8 + local window_height = radar2d.size / 3.5 + local window_pos = Vector2(center.x - window_width / 2, center.y + radar2d.size - window_height - SCREEN_BORDER) + local windowFlags = ui.WindowFlags {"NoTitleBar", "NoResize", "NoFocusOnAppearing", "NoBringToFrontOnFocus", "NoSavedSettings"} + ui.setNextWindowPos(window_pos, "Always") + ui.setNextWindowPadding(Vector2(0)) + ui.setNextWindowSize(Vector2(window_width, window_height), "Always") + + ui.window("radar_buttons", windowFlags, function() + + -- Draw radar mode toggle button + local icon = shouldDisplay2DRadar and radar3d.icon or radar2d.icon + local clicked = ui.mainMenuButton(icon, lui.HUD_RADAR_TOGGLE_MODE, false, Vector2(window_height)) + if toggle_radar or clicked then + shouldDisplay2DRadar = not shouldDisplay2DRadar + end + + -- Draw zoom mode indicator + if not shouldDisplay2DRadar then + local button_size = window_height / 1.5 + local tt = radar3d.auto_zoom and lui.HUD_RADAR_ZOOM_MODE_AUTOMATIC or lui.HUD_RADAR_ZOOM_MODE_MANUAL + ui.sameLine() + ui.addCursorPos(Vector2(0, window_height - button_size)) + icon = instrument:isAutoZoom() and icons.radar_automatic or icons.radar_manual + ui.mainMenuButton(icon, tt, ui.theme.buttonColors.disabled, Vector2(button_size)) + end + + -- Draw radar range + local distance = ui.Format.Distance(instrument:getZoom()) + local textpos = ui.getWindowPos() + Vector2(window_width, window_height) + ui.addStyledText(textpos, ui.anchor.right, ui.anchor.bottom, distance, colors.frame, pionillium.small, lui.HUD_RADAR_DISTANCE, colors.lightBlackBackground) + + end) -- window + +end -- function displayRadar() -- reset radar to default at game end -Event.Register("onGameEnd", function() shouldDisplay2DRadar = false end) +Event.Register("onGameEnd", function() + shouldDisplay2DRadar = false + radar2d:resetZoom() + radar3d:resetZoom() +end) -- save/load preference require 'Serializer':Register("PiguiRadar", @@ -335,7 +386,6 @@ ui.registerModule("game", { id = "game-view-radar-module", draw = displayRadar, debugReload = function() - print("Radar module reloading..") package.reimport() end }) diff --git a/data/pigui/modules/ship-internals-window.lua b/data/pigui/modules/ship-internals-window.lua index 36d896ec8a..fc3b57168d 100644 --- a/data/pigui/modules/ship-internals-window.lua +++ b/data/pigui/modules/ship-internals-window.lua @@ -106,7 +106,7 @@ local function displayShipFunctionWindow() assert(thrust_widget_size.y >= mainButtonSize.y) local window_width = ui.getWindowPadding().x * 2 + (mainButtonSize.x + ui.getItemSpacing().x) * buttons + thrust_widget_size.x local window_height = thrust_widget_size.y + ui.getWindowPadding().y * 2 - local window_posx = ui.screenWidth/2 - ui.reticuleCircleRadius - window_width + 12 -- manual move a little closer to the center + local window_posx = ui.screenWidth/2 - ui.reticuleCircleRadius - window_width local window_posy = ui.screenHeight - window_height ui.setNextWindowPos(Vector2(window_posx, window_posy), "Always") ui.window("ShipFunctions", windowFlags, function() @@ -128,6 +128,12 @@ local function displayShipFunctionWindow() end) end -ui.registerModule("game", { id = "ship-internals-window", draw = displayShipFunctionWindow }) +ui.registerModule("game", { + id = "ship-internals-window", + draw = displayShipFunctionWindow, + debugReload = function() + package.reimport() + end, +}) return {}