Adding UI for city editing

This commit is contained in:
wan-may 2024-07-20 00:09:53 -03:00
parent 6483f42c80
commit 1f2899077c
16 changed files with 351 additions and 45 deletions

View File

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

BIN
icons/check.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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 381 B

View File

@ -58,10 +58,7 @@ end
function love.mousepressed( x, y, mouseButton, istouch, presses ) function love.mousepressed( x, y, mouseButton, istouch, presses )
local wx, wy = Camera.GetWorldCoordinate( x, y ) local wx, wy = Camera.GetWorldCoordinate( x, y )
if button.selected and button.selected:contains( x, y ) then return button.mousepressed( x, y )
button.callback( button.selected )
return button.selected:callback()
end
end end
function love.mousemoved( x, y, dx, dy, istouch ) function love.mousemoved( x, y, dx, dy, istouch )
@ -89,9 +86,6 @@ function love.keypressed(key, code, isRepeat)
end end
end end
function love.textinput()
do
end end

View File

@ -53,6 +53,48 @@ function city:formatDisplayInfo()
CAPITAL: %s]]):format( self.name, self.country, self.x, self.y, self.pop, tostring(self.capital) ) CAPITAL: %s]]):format( self.name, self.country, self.x, self.y, self.pop, tostring(self.capital) )
end end
function city:delete()
end
function city:add()
local n = #cities + 1
cities[ n ] = self
self.n = n
local idxPoints = #points + 1
self.points = idxPoints
points[ idxPoints ], points[ idxPoints + 1 ] = self.x, self.y
end
function city:moveTo(x, y)
self.x, self.y = x, y
if self.points then
points[ self.points ] = x
points[ self.points + 1 ] = y
end
if self.capital then
caps[ self.caps ] = x
caps[ self.caps + 1 ] = y
end
end
function city:toggleCapital()
self.capital = not( self.capital )
end
function t.newCity( tbl )
return setmetatable({
name = "",
country = "",
x = 0,
y = 0,
pop = 0,
capital = false,
}, citymt )
end
function t.load( filename ) function t.load( filename )
print( "=== LOADING CITIES. ===" ) print( "=== LOADING CITIES. ===" )
@ -72,7 +114,8 @@ function t.load( filename )
local city = setmetatable({ local city = setmetatable({
name = line:sub( 1, 39 ):gsub("%s+$",""), name = line:sub( 1, 39 ):gsub("%s+$",""),
country = line:sub( 42, 82 ):gsub("%s+$",""), country = line:sub( 42, 82 ):gsub("%s+$",""),
x = x, y = y, pop = pop, capital = capital x = x, y = y, pop = pop, capital = capital,
n = n, points = idxPts, caps = capital and idxCaps
}, citymt ) }, citymt )
cities[n] = city cities[n] = city
n = n + 1 n = n + 1

View File

@ -4,12 +4,13 @@ local t = {
name = "", name = "",
tooltip = "", tooltip = "",
icon = false, icon = false,
lit = false,
x = 8, x = 8,
y = 250, y = 250,
w = 176, w = 13 * 28 - 4,
h = 24, h = 24,
group = "", group = false,
visible = true, visible = false,
callback = function( self ) return print( "clicked button: ", self.name, self.x, self.y, self.w, self.h, self.visible ) end callback = function( self ) return print( "clicked button: ", self.name, self.x, self.y, self.w, self.h, self.visible ) end
} }
t.selected, t.next, t.prev = t, t, t t.selected, t.next, t.prev = t, t, t
@ -52,7 +53,7 @@ function t.draw( b )
0, 0,
b.h / h ) b.h / h )
end end
if t.selected == b then if b.lit or t.selected == b then
b:highlight() b:highlight()
end end
end end
@ -92,15 +93,27 @@ function t.selectPrevInGroup()
repeat t.selectPrev() until group == t.selected.group repeat t.selectPrev() until group == t.selected.group
end end
function t.displayGroup( group, show ) --show/hide all buttons in a group
--passing hide=true will hide the group, hide=false or nil will show the group
--solo=true will hide all buttons outside the group
function t.displayGroup( group, hide, solo )
local b = t local b = t
repeat repeat
b = b.next b = b.next
b.visible = ( b.group == group ) local inGroup = (group == b.group)
if solo or inGroup then
b.visible = not(hide) and inGroup
end
until b == t until b == t
t.visible = true t.visible = true
end end
function t.mousepressed( x, y )
if t.selected and t.selected:contains( x, y ) then
return t.selected:callback()
end
end
function t.deselect( b ) function t.deselect( b )
t.selected = t t.selected = t
end end

View File

@ -8,7 +8,7 @@ local loadLocation = false
local folder = love.graphics.newImage( "icons/load.png" ) local folder = love.graphics.newImage( "icons/load.png" )
folder:setFilter( "nearest", "nearest" ) folder:setFilter( "nearest", "nearest" )
local loadButton = button.new{ local loadButton = button.new{
group = "loadModal", group = t,
name = "load", name = "load",
callback = function() callback = function()
if not loadLocation then return end if not loadLocation then return end
@ -25,7 +25,7 @@ local loadButton = button.new{
local xIcon = love.graphics.newImage( "icons/x.png" ) local xIcon = love.graphics.newImage( "icons/x.png" )
xIcon:setFilter( "nearest", "nearest" ) xIcon:setFilter( "nearest", "nearest" )
local cancelButton = button.new{ local cancelButton = button.new{
group = "loadModal", group = t,
name = "cancel load", name = "cancel load",
visible = false, visible = false,
icon = xIcon, icon = xIcon,
@ -41,7 +41,7 @@ function t.start()
loadLocation = loadLocation or map.path loadLocation = loadLocation or map.path
button.selected = loadButton button.selected = loadButton
loadButton.name = "save to "..loadLocation loadButton.name = "save to "..loadLocation
button.displayGroup( "loadModal", true ) button.displayGroup( t, true )
end end
function t.draw() function t.draw()

View File

View File

@ -1 +1,231 @@
local love = assert( love )
local lg = assert( love ).graphics
local utf8 = require 'utf8'
local button = require 'ui.button'
local textinput = require 'ui.textinput' local textinput = require 'ui.textinput'
local modal = require 'ui.modal'
local map = require 'map.map'
local camera = require 'ui.camera'
local t = {}
local city
local function keypressed( key, code, isRepeat )
if code == 'escape' then return modal.current:stop() end
if modal.previous then return modal.previous.keypressed( key, code, isRepeat ) end
end
--select modal: as normal, but clicking a city will proceed to the previously selected mode,
--and clicking escape will clear the previously selected mode
local selectModal = modal.new{}
local textModal = modal.new{}
local numberModal = modal.new{ cursor = 1 }
local editModal = modal.new{ mousepressed = button.mousepressed, keypressed = keypressed, }
local moveModal = modal.new{ mousepressed = button.mousepressed, keypressed = keypressed, mousemoved = button.selectIn }
local deleteModal = modal.new{ mousepressed = button.mousepressed, keypressed = keypressed }
local currentMode
button.new{ name = "NEW CITY",
group = t,
icon = lg.newImage("icons/layer-cities.png"),
x = 615,
y = 0,
callback = function()
city = map.cities.newCity()
return editModal:start()
end
}
moveModal.button = button.new{ name = "MOVE CITY",
group = t,
x = 615,
y = 28,
callback = function( self )
self.lit = true
selectModal.mode = moveModal
return selectModal:start()
end,
}
editModal.button = button.new{ name = "EDIT CITY",
group = t,
x = 615,
y = 28 * 2,
callback = function( self )
self.lit = true
selectModal.mode = editModal
return selectModal:start()
end,
}
deleteModal.button = button.new{ name = "DELETE CITY",
group = t,
x = 615,
y = 28 * 3,
icon = lg.newImage("icons/x.png"),
callback = function( self )
self.lit = true
selectModal.mode = deleteModal
return selectModal:start()
end,
}
--editButtons
local function editText( self )
print( "editing: ", self.field, city.name )
return textModal:start( self.field )
end
local function editNumber( self )
local field = self.field
end
local editButtons = {
save = button.new{
icon = lg.newImage( "icons/save.png" ),
callback = function() print( "stop editing city" ) return editModal:stop() end,},
name = button.new{ callback = editText },
country = button.new{ callback = editText },
x = button.new{ callback = editNumber },
y = button.new{ callback = editNumber },
capital = button.new{ callback = function() if city then return city:toggleCapital() end end },
}
do
local i = 0
for key, b in pairs( editButtons ) do
b.field = key
b.name = key
b.group = editModal
b.x = 0
b.y = 28 * i
i = i + 1
end
end
function editModal:start()
modal.start( self )
button.displayGroup( self, false, true )
for k, b in pairs( editButtons ) do
b.name = city[k] or b.name
end
end
function editModal.draw()
return button:draw()
end
function moveModal.update( dt )
local x, y = love.mouse.getPosition()
if y > 200 and love.mouse.isDown( 1 ) then
local wx, wy = camera.GetWorldCoordinate( x, y )
city:moveTo( wx, wy )
end
end
function moveModal.mousemoved( x, y, dx, dy, istouch )
return button.selectIn( x, y )
end
function moveModal.mousepressed( x, y, mouseButton, istouch, presses )
if y < 200 then
moveModal:stop()
return button.mousepressed( x, y, mouseButton, istouch, presses )
end
if map.selected and mouseButton == 2 then
city = map.selected
end
end
function numberModal:start( field )
self.field = field
return modal.start( self )
end
function numberModal.keypressed( key, code, isrepeat )
if code == 'backspace' then end
if code == 'escape' then return numberModal:stop() end
end
function numberModal.textinput( char )
local str = tostring( city[ numberModal.field ] )
local plus = str..char
if tonumber( plus ) then
city[ numberModal.field ] = plus
editButtons[ numberModal.field ].name = plus
end
end
function textModal.textinput( char )
print( "text input: ", char )
city[textModal.field] = city[textModal.field] .. char
editButtons[textModal.field].name = city[textModal.field]
end
function textModal.mousepressed()
end
function textModal:stop()
self.field = nil
return modal.stop( self )
end
function textModal:start( field )
self.field = field
return modal.start( self )
end
function textModal.keypressed( key, code, isRepeat )
if code == "backspace" then
local text = city[textModal.field]
-- get the byte offset to the last UTF-8 character in the string.
local byteoffset = utf8.offset(text, -1)
print( "textmodal: backspace", byteoffset )
if byteoffset then
-- remove the last UTF-8 character.
-- string.sub operates on bytes rather than UTF-8 characters, so we couldn't do string.sub(text, 1, -2).
city[textModal.field] = text:sub( 1, byteoffset - 1)
editButtons[textModal.field].name = city[textModal.field]
end
end
if code == "escape" then
return textModal:stop()
end
end
function selectModal.keypressed( key, code, isRepeat )
if code == 'escape' then return selectModal:stop() end
if modal.previous then return modal.previous.keypressed( key, code, isRepeat ) end
end
function selectModal:stop()
local mode = selectModal.mode
if mode then
mode.button.lit = false
end
return modal.stop( self )
end
function selectModal.mousepressed( x, y, mouseButton, istouch, presses )
if y < 200 then
selectModal:stop()
return button.mousepressed( x, y, mouseButton, istouch, presses )
end
if map.selected then
city = map.selected
return selectModal.mode:start()
end
end
return t

View File

@ -5,6 +5,7 @@ local savemodal = require 'ui.savemodal'
local loadmodal = require 'ui.loadmodal' local loadmodal = require 'ui.loadmodal'
local Camera = require 'ui.camera' local Camera = require 'ui.camera'
local map = require 'map.map' local map = require 'map.map'
local t = {}
local loadImg = love.graphics.newImage local loadImg = love.graphics.newImage
local layers = { local layers = {
@ -25,20 +26,23 @@ local layers = {
button.new{ button.new{
name = "LOAD", x = 250, y = 0, name = "LOAD", x = 250, y = 0,
w = 28 * 13, group = t,
callback = loadmodal.start, callback = loadmodal.start,
icon = love.graphics.newImage( "icons/load.png" )} icon = love.graphics.newImage( "icons/load.png" )}
button.new{ button.new{
name = "SAVE", x = 250, y = 28, name = "SAVE", x = 250, y = 28,
w = 28 * 13, group = t,
callback = savemodal.start, callback = savemodal.start,
icon = love.graphics.newImage( "icons/save.png" )} icon = love.graphics.newImage( "icons/save.png" )}
button.new{ button.new{
name = "UNDO", x = 250, y = 2 * 28, name = "UNDO", x = 250, y = 2 * 28,
w = 28 * 13, group = t,
callback = map.undo, callback = map.undo,
icon = love.graphics.newImage( "icons/undo.bmp" ) } icon = love.graphics.newImage( "icons/undo.bmp" ) }
local editButtons = {}
local layerButtons = {}
local showButtons = {} local showButtons = {}
local visibilityIcon = love.graphics.newImage( "icons/eye.bmp" ) local visibilityIcon = love.graphics.newImage( "icons/eye.bmp" )
local function updateVisibilityIcons() local function updateVisibilityIcons()
@ -57,9 +61,17 @@ end
local activeLayerButton local activeLayerButton
local function back( self ) local function back( self )
self.visible = false activeLayerButton.lit = false
map.setEditLayer()
activeLayerButton = nil activeLayerButton = nil
map.setEditLayer()
button.displayGroup( t, false, true )
for i, b in ipairs( editButtons ) do
b.visible = true
end
for i, b in ipairs( showButtons ) do
b.visible = true
end
self.visible = false
return updateVisibilityIcons() return updateVisibilityIcons()
end end
@ -68,13 +80,18 @@ local backButton = button.new{
visible = false, visible = false,
y = 5 * 28, y = 5 * 28,
x = 250, x = 250,
group = false,
icon = love.graphics.newImage( "icons/up.bmp" ), icon = love.graphics.newImage( "icons/up.bmp" ),
callback = back, callback = back,
} }
local function editLayer( self ) local function editLayer( self )
self.lit = true
map.setEditLayer( self.layer ) map.setEditLayer( self.layer )
activeLayerButton = self activeLayerButton = self
if self.menu then
button.displayGroup( self.menu )
end
backButton.visible = true backButton.visible = true
return updateVisibilityIcons() return updateVisibilityIcons()
end end
@ -86,11 +103,7 @@ local function copy( i, target )
return target return target
end end
local layerButtons = {}
local x = 250 local x = 250
local soloButtons = {}
local editButtons = {}
for i = 1, #layers do for i = 1, #layers do
editButtons[i] = button.new( copy( i, { editButtons[i] = button.new( copy( i, {
@ -98,7 +111,7 @@ for i = 1, #layers do
x = x + (button.h + 4) * ( i - 1 ), x = x + (button.h + 4) * ( i - 1 ),
w = 24, w = 24,
callback = editLayer, callback = editLayer,
group = "edit", group = editButtons,
tooltip = "edit "..layers[i].layer tooltip = "edit "..layers[i].layer
})) }))
layerButtons[ 2 * i - 2 ] = editButtons[i] layerButtons[ 2 * i - 2 ] = editButtons[i]
@ -110,14 +123,13 @@ for i = 1, #layers do
name = "", name = "",
callback = toggleVisibleLayer, callback = toggleVisibleLayer,
icon = visibilityIcon, icon = visibilityIcon,
group = "show", group = showButtons,
tooltip = "show "..layers[i].layer tooltip = "show "..layers[i].layer
})) }))
layerButtons[ 2 * i - 1 ] = showButtons[i] layerButtons[ 2 * i - 1 ] = showButtons[i]
end end
local t = {}
function t.draw() function t.draw()
--Status bar. --Status bar.
@ -144,16 +156,22 @@ function t.draw()
if map.selected then love.graphics.print( map.selected:formatDisplayInfo(), 0, 80 ) end if map.selected then love.graphics.print( map.selected:formatDisplayInfo(), 0, 80 ) end
if map.selectionLocked then end if map.selectionLocked then end
love.graphics.setScissor( 0, 0, love.graphics.getWidth(), 200 ) love.graphics.setScissor( 250, 0, love.graphics.getWidth() - 250, 200 )
love.graphics.rectangle( "line", 0, 0 , 250, 200 ) love.graphics.rectangle( "line", 0, 0 , 250, 200 )
love.graphics.rectangle( "line", 250, 0, love.graphics.getWidth() - 250, 200 ) love.graphics.rectangle( "line", 250, 0, love.graphics.getWidth() - 250, 200 )
love.graphics.rectangle( "line", 250, 0, button.w, 200 )
love.graphics.setColor( 1, 1, 1, 0.8 ) love.graphics.setColor( 1, 1, 1, 0.8 )
button:draw() button:draw()
love.graphics.setColor( 1, 0, 0, 0.4 ) love.graphics.setColor( 1, 0, 0, 0.4 )
if activeLayerButton then activeLayerButton:highlight() end end
do --button visibility
button.displayGroup( t, false, true )
button.displayGroup( editButtons, false, false )
button.displayGroup( layerButtons, false, false )
button.displayGroup( showButtons, false, false )
end end
return t return t

View File

@ -5,14 +5,17 @@ t.__index = t
local i = 0 local i = 0
function t.start( self ) function t.start( self )
print( "starting modal:", i + 1)
love.graphics.setScissor( love.graphics.setScissor(
self.x or 0, self.x or 0,
self.y or 0, self.y or 0,
self.w or love.graphics.getWidth(), self.w or love.graphics.getWidth(),
self.h or love.graphics.getDimensions()) self.h or love.graphics.getHeight())
i = i + 1 i = i + 1
t[i] = t[i] or {} t[i] = { modal = self }
t.previous = t[i]
t.current = self
--store callbacks --store callbacks
for name in pairs( self ) do for name in pairs( self ) do
@ -31,10 +34,11 @@ function t.start( self )
end end
function t.stop( self ) function t.stop( self )
print( "stopping modal:", i )
--restore callbacks --restore callbacks
for name in pairs( self ) do for name in pairs( self ) do
if love[name] then if love[name] then
love[name] = t[i][name] love[name] = t[i][name] or love[name]
end end
end end
@ -43,12 +47,16 @@ function t.stop( self )
button.deselect() button.deselect()
repeat repeat
b = b.next b = b.next
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 b.visible = t[i][b] or false
until b == button until b == button
t.current = t[i].modal
t[i] = nil t[i] = nil
i = i - 1 i = i - 1
t.previous = t[i - 1]
love.graphics.setScissor(0, 0, love.graphics.getDimensions())
end end
function t.new( modal ) function t.new( modal )

View File

@ -8,7 +8,7 @@ local saveLocation = false
local floppy = love.graphics.newImage( "icons/save.png" ) local floppy = love.graphics.newImage( "icons/save.png" )
floppy:setFilter( "nearest", "nearest" ) floppy:setFilter( "nearest", "nearest" )
local saveButton = button.new{ local saveButton = button.new{
group = "saveModal", group = t,
name = "save", name = "save",
callback = function() map.save(); return t:stop() end, callback = function() map.save(); return t:stop() end,
visible = false, visible = false,
@ -22,7 +22,7 @@ local saveButton = button.new{
local xIcon = love.graphics.newImage( "icons/x.png" ) local xIcon = love.graphics.newImage( "icons/x.png" )
xIcon:setFilter( "nearest", "nearest" ) xIcon:setFilter( "nearest", "nearest" )
local cancelButton = button.new{ local cancelButton = button.new{
group = "saveModal", group = t,
name = "cancel", name = "cancel",
visible = false, visible = false,
icon = xIcon, icon = xIcon,
@ -38,7 +38,7 @@ function t.start()
saveLocation = saveLocation or map.path saveLocation = saveLocation or map.path
button.selected = saveButton button.selected = saveButton
saveButton.name = "save to "..saveLocation saveButton.name = "save to "..saveLocation
button.displayGroup( "saveModal", true ) button.displayGroup( t, false, true )
end end
function t.draw() function t.draw()

View File

@ -54,7 +54,7 @@ function t.keypressed(key, code, isRepeat)
if byteoffset then if byteoffset then
-- remove the last UTF-8 character. -- remove the last UTF-8 character.
-- string.sub operates on bytes rather than UTF-8 characters, so we couldn't do string.sub(text, 1, -2). -- string.sub operates on bytes rather than UTF-8 characters, so we couldn't do string.sub(text, 1, -2).
text = string.sub(text, 1, byteoffset - 1) t.currentModal.currentField = text:sub( 1, byteoffset - 1)
end end
end end
if code == "escape" then if code == "escape" then