move UI for horizontal aspect ratio, add load modal and new icons, tweak camera

This commit is contained in:
wan-may 2024-07-18 17:00:51 -03:00
parent bfed3af852
commit 97f21e192f
30 changed files with 305 additions and 221 deletions

View File

@ -12,7 +12,7 @@ function love.conf(t)
t.window.title = "dcEarth" -- The window title (string)
t.window.icon = "icons/favicon.png" -- Filepath to an image to use as the window's icon (string)
t.window.width = 800 -- The window width (number)
t.window.width = 1000 -- The window width (number)
t.window.height = 640 -- The window height (number)
t.window.borderless = false -- Remove all border visuals from the window (boolean)
t.window.resizable = true -- Let the window be user-resizable (boolean)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
icons/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
icons/layer-ainodes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
icons/layer-cities.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

BIN
icons/layer-coastlines.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

BIN
icons/layer-sailable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

BIN
icons/layer-travelnodes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

BIN
icons/load.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -17,14 +17,7 @@ local exists, mkdir, PATH_SEPARATOR
if ffi.os == "Windows" then
ffi.cdef[[
bool CreateDirectoryA(const char *path, void *lpSecurityAttributes);
int _access(const char *path, int mode);
]]
function exists(path)
assert(type(path) == "string", "path isn't a string")
local result = C._access(path, 0) -- Check existence
return result == 0
end
function mkdir(path, _)
assert(type(path) == "string", "path isn't a string")
if not C.CreateDirectoryA(path, nil) then
@ -36,13 +29,7 @@ if ffi.os == "Windows" then
elseif ffi.os == "Linux" or ffi.os == "OSX" then
ffi.cdef[[
int mkdir(const char *path, int mode);
int access(const char *path, int amode);
]]
function exists(path)
assert(type(path) == "string", "path isn't a string")
local result = C.access(path, 0) -- Check existence
return result == 0
end
function mkdir(path, mode)
assert(type(path) == "string", "path isn't a string")
local mode = tonumber(mode or "755", 8)
@ -94,6 +81,18 @@ local function mkdirs(path)
end
end
--- Check if a file or directory exists in this path
function exists(file)
local ok, err, code = os.rename(file, file)
if not ok then
if code == 13 then
-- Permission denied, but it exists
return true
end
end
return ok, err
end
return {
exists = exists,
join = join,

View File

@ -1,9 +1,9 @@
local love = assert( love, "This tool requires LOVE: love2d.org" )
--assert( require('mobdebug') ).start() --remote debugger
local map = require 'map'
local button = require 'button'
require 'mainmenu'
local Camera = require 'camera'
local map = require 'map.map'
local button = require 'ui.button'
local mainmenu = require 'ui.mainmenu'
local Camera = require 'ui.camera'
function love.load()
love.filesystem.setIdentity( "dcearth", false )
@ -44,29 +44,7 @@ function love.draw()
map.draw()
love.graphics.pop()
--Status bar.
local x, y = love.mouse.getPosition()
local wx, wy = Camera.GetWorldCoordinate( x, y )
local bx, by = Camera.GetBitmapCoordinate( x, y )
local h = love.graphics.getHeight() - 60
love.graphics.setColor( 0, 0, 0, 0.9 )
love.graphics.rectangle( "fill", 0, 0, 250, love.graphics.getHeight() )
love.graphics.setColor( 1, 1, 1, 1 )
love.graphics.print(([[
SCREEN %-12d %-12d
WORLD %-12.2f%-12.2f
BITMAP %-12d %-12d
%s]]):format(x, y, wx, wy, bx, by, map.editLayer and map.editLayer.filename or ""), 0, 0)
if map.selected then love.graphics.print( map.selected:formatDisplayInfo(), 0, 80 ) end
if map.selectionLocked then end
love.graphics.rectangle( "line", 0, 0 , 250, 218 )
love.graphics.rectangle( "line", 0, 218, 250, love.graphics.getHeight() )
love.graphics.setColor( 1, 1, 1, 0.6 )
button:draw()
mainmenu.draw()
end
function love.resize(w, h)
@ -80,8 +58,7 @@ end
function love.mousepressed( x, y, mouseButton, istouch, presses )
local wx, wy = Camera.GetWorldCoordinate( x, y )
if button.selected and button.selected:contains( x, y ) then
print( ("MOUSE\tx %f\ty %f\twx %f\twy %f"):format(x, y, wx, wy) )
if button.selected and button.selected:contains( x, y ) then
button.callback( button.selected )
return button.selected:callback()
end
@ -101,10 +78,10 @@ end
function love.keypressed(key, code, isRepeat)
if code == "left" then return button.selectPrev() end
if code == "right" then return button.selectNext() end
if code == "down" then return button.selectNextInGroup() end
if code == "up" then return button.selectPrevInGroup() end
if code == "up" then return button.selectPrev() end
if code == "down" then return button.selectNext() end
if code == "right" then return button.selectNextInGroup() end
if code == "left" then return button.selectPrevInGroup() end
if code == "return" then return button.selected:callback() end
if key == "c" then

View File

@ -1,142 +0,0 @@
local love = assert( love )
local button = require 'button'
local savemodal = require 'savemodal'
local map = require 'map'
button.new{ name = "SAVE", y = 222, callback = savemodal.start, icon = love.graphics.newImage( "icons/save.png" )}
button.new{ name = "UNDO", y = 250, callback = map.undo, icon = love.graphics.newImage( "icons/undo.bmp" ) }
local tools
local layerButtons = {}
local function back( self )
for k, button in pairs( tools ) do button.visible = false end
for k, button in pairs( layerButtons ) do button.visible = true end
self.visible = false
map.editLayer = false
end
local backButton = button.new{
name = "UP",
visible = false,
y = 250 + button.h + 4,
icon = love.graphics.newImage( "icons/up.bmp" ),
callback = back,
}
local function toolCallback( self )
local f = (map.layers[self.layer])[self.name]
if f then return f(self) end
end
tools = {
button.new{ name = "SELECT"},
button.new{ name = "ERASE",},
button.new{ name = "MOVE", },
button.new{ name = "ADD", },
button.new{ name = "EDIT", },
button.new{ name = "DRAW", },
}
for i, v in ipairs( tools ) do
v.callback = toolCallback
v.y = 250 + (v.h + 4) * ( i + 1 )
v.visible = false
end
local layers = {
{ name = "AF", layer = "africa" },
{ name = "EU", layer = "europe" },
{ name = "NA", layer = "northamerica" },
{ name = "SA", layer = "southamerica" },
{ name = "AS", layer = "southasia" },
{ name = "RU", layer = "russia" },
{ name = "PATH", layer = "travelnodes" },
{ name = "AI", layer = "ainodes" },
{ name = "CITY", layer = "cities" },
{ name = "COAST", layer = "coastlines" },
{ name = "LOW", layer = "coastlinesLow"},
{ name = "INT", layer = "international"},
{ name = "SAIL", layer = "sailable" },
}
local showButtons = {}
local visibilityIcon = love.graphics.newImage( "icons/eye.bmp" )
local function updateVisibilityIcons()
for i = 1, #showButtons do
showButtons[i].icon = map.layers[ showButtons[i].layer ].visible and visibilityIcon
end
end
local function toggleVisibleLayer( self )
if not (self and self.layer) then return end
local ml = map.layers[ self.layer ]
ml.visible = not( ml.visible )
return updateVisibilityIcons()
end
local soloIcon = false--love.graphics.newImage( "icons/eye.bmp" )
local function soloVisibleLayer( self )
for k, layer in pairs( map.layers ) do
print( "invisible layer, map:", k, layer)
layer.visible = false
end
map.layers[ self.layer ].visible = true
return updateVisibilityIcons()
end
local function editLayer( self )
map.editLayer = map.layers[ self.layer ]
map.editLayer.visible = true
for k, button in pairs( layerButtons ) do button.visible = false end
for k, button in pairs( tools ) do
button.visible = true
button.layer = self.layer
end
backButton.visible = true
print( "EDITING LAYER", self.layer )
return updateVisibilityIcons()
end
local function copy( i, target )
for k, v in pairs( layers[i] ) do
target[k] = target[k] or v
end
return target
end
local y = 250
local soloButtons = {}
local editButtons = {}
for i = 1, #layers do
editButtons[i] = button.new( copy( i, {
x = 8,
y = y + (button.h + 4) * i,
w = 112,
callback = editLayer,
group = "edit",
}))
layerButtons[ 3 * i - 2 ] = editButtons[i]
showButtons[i] = button.new( copy( i, {
x = 128,
y = y + (button.h + 4) * i,
w = 24,
name = "",
callback = toggleVisibleLayer,
icon = visibilityIcon,
group = "show",
}))
layerButtons[ 3 * i - 1 ] = showButtons[i]
soloButtons[i] = button.new( copy( i, {
x = 160,
y = y + (button.h + 4) * i,
w = 24,
name = "S",
callback = soloVisibleLayer,
icon = soloIcon,
group = "solo",
}))
layerButtons[ 3 * i ] = soloButtons[i]
end

View File

@ -1,7 +1,7 @@
--Manage the AI nodes used by DEFCON.
local bmp = require 'bmp'
local bmp = require 'map.bmp'
local lg = assert( love.graphics )
local locationQuery = require 'locationQuery'
local locationQuery = require 'map.locationQuery'
local t = setmetatable( {}, {__index = locationQuery } )
local print = print

View File

@ -6,7 +6,7 @@ local table = table
local tonumber = tonumber
local lfs = love.filesystem
local lg = love.graphics
local locationQuery = require 'locationQuery'
local locationQuery = require 'map.locationQuery'
local cities
local points = {}
local caps = {}

View File

@ -87,11 +87,14 @@ function t.selectNearest( lines, wx, wy )
end
function t.save( lines )
local str = { "b" }
local str = { "b" } --initial B
for i, poly in ipairs( lines ) do
str[i + 1] = table.concat( poly, " " )
end
str = table.concat( str, "\nb\n" ):gsub("(%S+) (%S+) ", "%1 %2\n")
--concatenate into one big string, one line per polygon
--then put each pair of numbers on their own line (without concatenating)
--we use CRLF line breaks here because that's what's in the original game files
str = table.concat( str, "\13\nb\13\n" ):gsub("(%S+) (%S+) ", "%1 %2\13\n")
return str
end

View File

@ -2,12 +2,12 @@ local love = assert( love )
local io = io
local mkdir = assert( require 'lib.mkdir' )
local lg = love.graphics
local AI = require 'ai'
local Cities = require 'cities'
local Lines = require 'lines'
local Nodes = require 'travelNodes'
local Camera = require 'camera'
local Territory = require 'territory'
local AI = require 'map.ai'
local Cities = require 'map.cities'
local Lines = require 'map.lines'
local Nodes = require 'map.travelNodes'
local Camera = require 'ui.camera'
local Territory = require 'map.territory'
--flat list of editable layers for convenience
local layers = {
@ -33,7 +33,7 @@ local map = {
selected = false,
selectionLocked = false,
editLayer = false,
territory = {
africa = false,
europe = false,
@ -42,7 +42,7 @@ local map = {
southamerica = false,
southasia = false
},
background = false,
coastlines = false,
coastlinesLow = false,
@ -67,7 +67,7 @@ function map.load( path )
end
map.loaded = true
map.path = path
--update references
for k, v in pairs( layers ) do
layers[k] = map[k] or map.territory[k]
@ -75,6 +75,7 @@ function map.load( path )
end
function map.draw()
love.graphics.setScissor( 0, 200, love.graphics.getWidth(), love.graphics.getHeight() - 200 )
lg.clear( 0, 0, 0, 1 )
if not map.loaded then return end
@ -192,7 +193,8 @@ end
function map.save()
--should be cross platform-ish
for _, folder in ipairs{ "/data/", "/data/earth/", "/data/graphics/" } do
assert( mkdir.exists( map.path ) )
--getInfo checks if a directory exists
assert( mkdir.exists( map.path ), map.path )
local path = map.path..folder
if not mkdir.exists( path ) then mkdir.mkdir( path ) end
end
@ -211,4 +213,17 @@ function map.undo()
print( "=== UNDO ===" )
end
function map.setEditLayer( layerName )
if not layerName then
map.editLayer = nil
for name, layer in pairs( layers ) do layer.visible = true end
else
for name, layer in pairs( layers ) do
layer.visible = false
end
map.editLayer = layers[ layerName ]
if map.editLayer then map.editLayer.visible = true end
end
end
return map

View File

@ -1,5 +1,5 @@
local t = {}
local bmp = require 'bmp'
local bmp = require 'map.bmp'
local lg = assert( love.graphics )
local colours = {

View File

@ -2,8 +2,8 @@
--This is important for a mapping tool because the DEFCON client will not load a map unless
--the pathfinding nodes form a connected graph.
local bmp = require 'bmp'
local locationQuery = require 'locationQuery'
local bmp = require 'map.bmp'
local locationQuery = require 'map.locationQuery'
local lg = assert( love.graphics )

View File

@ -2,13 +2,13 @@ local lg = love.graphics
local t = {
name = "",
tooltip = "button",
tooltip = "",
icon = false,
x = 8,
y = 250,
w = 176,
h = 24,
group = false,
group = "",
visible = true,
callback = function( self ) return print( "clicked button: ", self.name, self.x, self.y, self.w, self.h, self.visible ) end
}
@ -29,13 +29,17 @@ function t.new( b )
return b
end
function t.highlight( b )
lg.rectangle( "fill", b.x, b.y, b.w, b.h )
end
local drawPassOngoing = false
function t.draw( b )
if b == t then
drawPassOngoing = not( drawPassOngoing )
if not drawPassOngoing then return end
elseif b.visible then
lg.rectangle( "line", b.x, b.y, b.w, b.h, 6 )
lg.rectangle( "line", b.x, b.y, b.w, b.h )
lg.printf( b.name,
b.x + (b.icon and b.h or 0),
b.y + 0.5 * ( b.h - lg.getFont():getHeight() ),
@ -49,7 +53,7 @@ function t.draw( b )
b.h / h )
end
if t.selected == b then
lg.rectangle( "fill", b.x, b.y, b.w, b.h, 6 )
b:highlight()
end
end
return t.draw( b.next )
@ -74,7 +78,9 @@ end
function t.selectNextInGroup()
--make sure our group is visible, otherwise the loop doesn't end
local group = t.selected and t.selected.visible and t.selected.group
local group = t.selected
group = group and t.selected.visible
group = group and t.selected.group
if not group then return t.selectNext() end
repeat t.selectNext() until group == t.selected.group
end

View File

@ -3,7 +3,7 @@ local tfTerritory = love.math.newTransform()
local tfNodes = love.math.newTransform()
local lg = assert( love.graphics )
local Camera = {
x = -90, y = 45,
x = 0, y = 70,
w = 360, h = 200,
zoom = 1, tf = tf,
tfTerritory = tfTerritory, tfNodes = tfNodes }
@ -37,7 +37,9 @@ end
function Camera.Translate( x, y )
x = x or 0
y = y or 0
return Camera.Set( math.max(-180, math.min(360, Camera.x + x)), math.min(100, Camera.y + y), Camera.w, Camera.h)
return Camera.Set(
math.max(-360, math.min(360, Camera.x + x)),
math.max(-140, math.min(140, Camera.y + y)), Camera.w, Camera.h)
end
--In world coordinates: top left corner at x, y, extent of 1/w, 1/h.
@ -48,14 +50,17 @@ function Camera.Set( x, y, w, h )
tf:scale( w / 360, -h / 200 )
tf:translate( 180 - x, -y - 100 )
tfTerritory:reset()
tfTerritory:scale( w / 512, h / 285 )
tfTerritory:translate( -x * 512 / 360, y * 512 / 360 )
tfNodes:reset()
tfNodes:scale( w / 360, -h / 200 )
tfNodes:translate( 180 - x , -y - 100 )
--tfNodes:translate( -x * 800 / 360, y * 400 / 200 )
end
function Camera.Resize( w, h )

61
ui/loadmodal.lua Normal file
View File

@ -0,0 +1,61 @@
local love = assert( love )
local modal = require( "ui.modal" )
local button = require( "ui.button" )
local map = require( "map.map" )
local t = {}
local loadLocation = false
local folder = love.graphics.newImage( "icons/load.png" )
folder:setFilter( "nearest", "nearest" )
local loadButton = button.new{
group = "loadModal",
name = "load",
callback = function()
if not loadLocation then return end
map.load( loadLocation )
return t:stop() end,
visible = false,
icon = folder,
x = love.graphics.getWidth() / 2 - 300,
y = love.graphics.getHeight() / 2 - 150,
w = 600,
h = 100,
}
local xIcon = love.graphics.newImage( "icons/x.png" )
xIcon:setFilter( "nearest", "nearest" )
local cancelButton = button.new{
group = "loadModal",
name = "cancel load",
visible = false,
icon = xIcon,
callback = function() return t:stop() end,
x = love.graphics.getWidth() / 2 - 300,
y = love.graphics.getHeight() / 2,
w = 600,
h = 100
}
function t.start()
modal.start( t )
loadLocation = loadLocation or map.path
button.selected = loadButton
loadButton.name = "save to "..loadLocation
button.displayGroup( "loadModal", true )
end
function t.draw()
love.graphics.clear( 0,0,0,1 )
love.graphics.setColor( 0, 0, 1, 0.4 )
button:draw()
end
function t.directorydropped( path )
loadLocation = path
map.path = path
loadButton.name = "load from "..map.path
return love.filesystem.mount( path, "" )
end
return modal.new( t )

158
ui/mainmenu.lua Normal file
View File

@ -0,0 +1,158 @@
local love = assert( love )
local button = require 'ui.button'
local savemodal = require 'ui.savemodal'
local loadmodal = require 'ui.loadmodal'
local Camera = require 'ui.camera'
local map = require 'map.map'
button.new{
name = "LOAD", x = 250, y = 0,
w = 28 * 13,
callback = loadmodal.start,
icon = love.graphics.newImage( "icons/load.png" )}
button.new{
name = "SAVE", x = 250, y = 28,
w = 28 * 13,
callback = savemodal.start,
icon = love.graphics.newImage( "icons/save.png" )}
button.new{
name = "UNDO", x = 250, y = 2 * 28,
w = 28 * 13,
callback = map.undo,
icon = love.graphics.newImage( "icons/undo.bmp" ) }
local layers = {
{ name = "AF", layer = "africa" },
{ name = "EU", layer = "europe" },
{ name = "NA", layer = "northamerica" },
{ name = "SA", layer = "southamerica" },
{ name = "AS", layer = "southasia" },
{ name = "RU", layer = "russia" },
{ name = "PATH", layer = "travelnodes" , icon = love.graphics.newImage( "icons/layer-travelnodes.png" )},
{ name = "AI", layer = "ainodes" , icon = love.graphics.newImage( "icons/layer-ainodes.png" )},
{ name = "CITY", layer = "cities" , icon = love.graphics.newImage( "icons/layer-cities.png" )},
{ name = "COAST", layer = "coastlines" , icon = love.graphics.newImage( "icons/layer-coastlines.png" )},
{ name = "LOW", layer = "coastlinesLow", icon = love.graphics.newImage( "icons/layer-coastlines-low.png" )},
{ name = "INT", layer = "international", icon = love.graphics.newImage( "icons/layer-international.png" )},
{ name = "SAIL", layer = "sailable" , icon = love.graphics.newImage( "icons/layer-sailable.png" )},
}
local showButtons = {}
local visibilityIcon = love.graphics.newImage( "icons/eye.bmp" )
local function updateVisibilityIcons()
for i = 1, #showButtons do
showButtons[i].icon = map.layers[ showButtons[i].layer ].visible and visibilityIcon
end
end
local function toggleVisibleLayer( self )
if not (self and self.layer) then return end
local ml = map.layers[ self.layer ]
ml.visible = not( ml.visible )
return updateVisibilityIcons()
end
local activeLayerButton
local function back( self )
self.visible = false
map.setEditLayer()
activeLayerButton = nil
return updateVisibilityIcons()
end
local backButton = button.new{
name = "UP",
visible = false,
y = 5 * 28,
x = 250,
icon = love.graphics.newImage( "icons/up.bmp" ),
callback = back,
}
local function editLayer( self )
map.setEditLayer( self.layer )
activeLayerButton = self
backButton.visible = true
return updateVisibilityIcons()
end
local function copy( i, target )
for k, v in pairs( layers[i] ) do
target[k] = target[k] or v
end
return target
end
local layerButtons = {}
local x = 250
local soloButtons = {}
local editButtons = {}
for i = 1, #layers do
editButtons[i] = button.new( copy( i, {
y = 3 * 28,
x = x + (button.h + 4) * ( i - 1 ),
w = 24,
callback = editLayer,
group = "edit",
tooltip = "edit "..layers[i].layer
}))
layerButtons[ 2 * i - 2 ] = editButtons[i]
showButtons[i] = button.new( copy( i, {
y = 4 * 28,
x = x + (button.h + 4) * ( i - 1 ),
w = 24,
name = "",
callback = toggleVisibleLayer,
icon = visibilityIcon,
group = "show",
tooltip = "show "..layers[i].layer
}))
layerButtons[ 2 * i - 1 ] = showButtons[i]
end
local t = {}
function t.draw()
--Status bar.
love.graphics.setScissor( 0, 0, 250, 200 )
local x, y = love.mouse.getPosition()
local wx, wy = Camera.GetWorldCoordinate( x, y )
local bx, by = Camera.GetBitmapCoordinate( x, y )
local h = love.graphics.getHeight() - 60
love.graphics.setColor( 0, 0, 0, 1 )
love.graphics.rectangle( "fill", 0, 0, 250, love.graphics.getHeight() )
love.graphics.setColor( 1, 1, 1, 1 )
love.graphics.print(([[
SCREEN %-12d %-12d
WORLD %-12.2f%-12.2f
BITMAP %-12d %-12d
%s
%s]]):format(
x, y,
wx, wy,
bx, by,
button.selected and button.selected.tooltip or "",
map.editLayer and map.editLayer.filename or ""), 0, 0)
if map.selected then love.graphics.print( map.selected:formatDisplayInfo(), 0, 80 ) end
if map.selectionLocked then end
love.graphics.setScissor( 0, 0, love.graphics.getWidth(), 200 )
love.graphics.rectangle( "line", 0, 0 , 250, 200 )
love.graphics.rectangle( "line", 250, 0, love.graphics.getWidth() - 250, 200 )
love.graphics.setColor( 1, 1, 1, 0.8 )
button:draw()
love.graphics.setColor( 1, 0, 0, 0.4 )
if activeLayerButton then activeLayerButton:highlight() end
end
return t

View File

@ -1,10 +1,12 @@
local love = assert( love )
local button = require( "button" )
local button = require( "ui.button" )
local t = {}
t.__index = t
local i = 0
function t.start( self )
love.graphics.setScissor( 0, 0, love.graphics.getDimensions() )
i = i + 1
t[i] = t[i] or {}
@ -34,10 +36,10 @@ function t.stop( self )
--restore menus
local b = button
button.selected = button
button.deselect()
repeat
b = b.next
b.visible = t[i][b]
b.visible = t[i][b] or false --accessing a button's nil field is an error, so make sure that b.visible is a boolean
until b == button
t[i] = nil

View File

@ -1,7 +1,7 @@
local love = assert( love )
local modal = require( "modal" )
local button = require( "button" )
local map = require( "map" )
local modal = require( "ui.modal" )
local button = require( "ui.button" )
local map = require( "map.map" )
local t = {}
local saveLocation = false

View File

@ -1,6 +1,6 @@
local love = assert( love )
local utf8 = require("utf8")
local modal = require( "modal" )
local modal = require( "ui.modal" )
local t = modal.new{ }
function t.setCurrentModal( fields )