/love2d-menuengine

A Script to create a Menu in Love2D / LÖVE

Primary LanguageLuaMIT LicenseMIT

love2d-menuengine for LÖVE 11.1

A Script to create a Menu in Love2D / LÖVE, navigate-able through mouse and keyboard.

For a quick view, scroll down to see the Examples. It is - in my opinion - really easy to use^^.

API Reference

Constructor

menu = menuengine.new([x], [y], [font], [space], [align], [limit])

Returns an menu-object (see below).

x (0) X-Position. Optional but recommended.

y (0) Y-Position. Optional but recommended.

font (love.graphics.getFont()) Font. If no Font is configured, it will use the current Default-Font. Optional.

space (love.graphics.getFont():getHeight()) Spaces between Entries. Usually, it should be autodetected by the Font you are using, but sometimes it might be better to set this explicitly. Optional.

align ("left") Alignment of text. "left", "center" or "right"

limit (love.graphics.getWidth()) Wrap the line after this many horizontal pixels. Alignment is based on this value.


Add Entries

You can add Entries through the menu-Object:

entry = menu:addEntry(text, [func], [args], [font], [colorNormal], [colorSelected])

Returns an entry-object (see below).

text Displayed Text. Required.

func (nil) Function that will be called, if the User selects this Menupoint.

If you set menuengine.stop_on_nil_functions = true, a nil-Function will throw an Error. Note: On future Releases, a nil-func will automatically throw an Error and menuengine.stop_on_nil_functions will be removed!

args (New in 0.9.9e Beta) If User selects this Menupoint, it will call the given func with that given Arguments.

font Selected Font-Object for this Entry. Optional.

colorNormal ({1, 1, 1, nil}) RGBA-Values. If Entry is not highlighted, it will stay in that Color. Optional.

colorSelected ({.8, .4, .4}) RGBA-Values. If Entry is highlighted, it will stay in that Color. Optional.


Add empty Space

Just write

menu:addSep()

Returns an entry-object. No Parameters.


Entry-Objects

entry-Objects (created through addEntry from a menu-Object) have only attributes, no methods.

text text as String.

x, y X and Y as Integer. Text-Position.

font Font as Font-Object. Default-Font if not configured.

func function as callable Function. nil if not configured.

args Arguments that will pass through the function. nil if not configured.

symbolSelectedBegin, symbolSelectedEnd, normalSelectedBegin, normalSelectedEnd Strings to add on Begin/End of the Text ("symbol..." if selected, "normal..." if not selected).

For Example, if "text" is "Menuentry", "symbolSelectedBegin" is "[" and "symbolSelectedEnd" is "]", than it will show up as "[Menuentry]" if it's selected by the User.

sndMove, sndSuccess Sounds that will be played if User selects this Entry.

disabled if true, Entry won't show up and won't react by Selecting.


Settings

There are three so-called places where the Settings are stored.


Place 1: Menu-Wide "menuengine.settings"

Every Default-Option is stored on the table "menuengine.settings".


Place 2: Menu-Object

Every new Menu created by menuengine.new will adopt settings from the Menu-Wide "menuengine.settings".

Every menu-Object offers Methods and Attributes to change Settings of every entry that was created by it, but will not affect Settings in menuengine.new.


Place 3: Entry-Object

Every new Entry created by addEntry will adopt settings from the "menu"-Object that's created by it. Every Entry offers Attributes, but will not affect Settings on the menu-Object (and of course it will not affect Settings on menuengine.settings).


Menu-Methods (other than "addEntry" and "addSep")

Create a menu-Object by using menuengine.new. Be careful, these Methods are changing every Entry contains by the menu-Object. It also affects future Entry-Objects as well!


menu:movePosition(newx, newy)

Moves and Orders every Entry, that contains this object.

newx, newy Integer-Values, X- and Y-Coordinate on the upper left. Required.


menu:setFont(font, space)

Changes Font and Space for every Entry, that contains this object.

font Font-Object. Required.

space (font:getWidth()) Integer. Sets the Spaces between every Entry. Optional.

After using setFont, it may be required to call movePosition to rearrange Entries.


menu:setDisabled(value)

Enables or Disables every Entry. Draw- and Update-States will have no effects if disabled, and Entries won't shown as long as they are disabled.

value Boolean. Required.


menu:setColorNormal(color)

Set the Color of every Entry, that contains this object. Does affect Entries if they are not selected.

color Table of RGBA-Values. Required.


menu:setColorSelected(color)

Set the Color of every Entry, that contains this object. Does affect Entries if they are selected.

color Table of RGBA-Values. Required.


menu:moveCursor(d)

Moves the Cursor in the given Direction.

d Integer. Direction; -1 means "up", 1 means "down".


menu:setSndMove(sound)

Set the Sound of every Entry that will be played if the User moves through the Entries.

sound Sound-Object. If nil, no sound will be played.


menu:setSndSuccess(sound)

Set the Sound of every Entry that will be played if the User selects an Entry.

sound Sound-Object. If nil, no sound will be played.


Menu-Attributes

There's only two Attributes.

menu.target Defaults to nil. If set, it will called as a Function after the User selects an Entry (see below, "Example 2").

menu.cursor Integer. Represent actual Cursor-Position.

Be very careful of setting this Variable. It is useful for setting the Cursor in a Pre-defined Value (for example, 1 is always the first Entry), but it will not check on Errors! As say, be very careful with that Attribute!


Other Functions

menuengine.draw() Will draw EVERY Menu (if not disabled)

menuengine.update() Will update EVERY Menu (if not disabled)

menuengine.disable() Disables EVERY Menu.

menuengine.enable() Enables EVERY Menu.


Examples


Example 1

local menuengine = require "menuengine"
menuengine.stop_on_nil_functions = true

local text = "Nothing was selected."

-- Mainmenu
local mainmenu

-- Start Game
local function start_game()
    text = "Start Game was selected!"
end

-- Options
local function options()
    text = "Options was selected!"
end

-- Quit Game
local function quit_game()
    text = "Quit Game was selected!"
end

 -- ----------

function love.load()
    love.window.setMode(600,400)
    love.graphics.setFont(love.graphics.newFont(20))

    mainmenu = menuengine.new(200,100)
    mainmenu:addEntry("Start Game", start_game)
    mainmenu:addEntry("Options", options)
    mainmenu:addSep()
    mainmenu:addEntry("Quit Game", quit_game)
end

function love.update(dt)
    mainmenu:update()
end

function love.draw()
    love.graphics.clear()
    love.graphics.print(text)
    mainmenu:draw()
end

function love.keypressed(key, scancode, isrepeat)
    menuengine.keypressed(scancode)

    if scancode == "escape" then
        love.event.quit()
    end
end

function love.mousemoved(x, y, dx, dy, istouch)
    menuengine.mousemoved(x, y)
end

Example 2

Example with args-Usage.

local menuengine = require "menuengine"
menuengine.stop_on_nil_functions = true

local text = "Nothing was selected."

-- Mainmenu
local mainmenu

-- Function to start after User selects something
local function mainmenu_finish(entrypoint)
    if entrypoint == 1 then
        text = "Start Game was selected!"
    elseif entrypoint == 2 then
        text = "Options was selected!"
    elseif entrypoint == 3 then
        text = "Quit Game was selected!"
    end
end

 -- ----------

function love.load()
    love.window.setMode(600,400)
    love.graphics.setFont(love.graphics.newFont(20))

    mainmenu = menuengine.new(200,100)
    mainmenu:addEntry("Start Game", mainmenu_finish, 1)  -- call "mainmenu_finish", args = "1"
    mainmenu:addEntry("Options", mainmenu_finish, 2)  -- call "mainmenu_finish", args = "2"
    mainmenu:addSep()
    mainmenu:addEntry("Quit Game", mainmenu_finish, 3)  -- call "mainmenu_finish", args = "3"
end

function love.update(dt)
    mainmenu:update()
end

function love.draw()
    love.graphics.clear()
    love.graphics.print(text)
    mainmenu:draw()
end

function love.keypressed(key, scancode, isrepeat)
    menuengine.keypressed(scancode)

    if scancode == "escape" then
        love.event.quit()
    end
end

function love.mousemoved(x, y, dx, dy, istouch)
    menuengine.mousemoved(x, y)
end

Example 3

Example using love.graphics.scale. You need to scale Mouse-Coordinates before calling menuengine.mousemoved.

local menuengine = require "menuengine"

-- store Resolutions
local INTERNAL_RES = {}
INTERNAL_RES.x = 256
INTERNAL_RES.y = 144

local EXTERNAL_RES = {}
EXTERNAL_RES.x = 640
EXTERNAL_RES.y = 360

-- Mainmenu
local mainmenu

function love.load()
    love.window.setMode(EXTERNAL_RES.x, EXTERNAL_RES.y)
    love.graphics.setFont(love.graphics.newFont(12))

    mainmenu = menuengine.new(20,20)
    mainmenu:addEntry("Mouse works, as")
    mainmenu:addEntry("you can see, on")
    mainmenu:addEntry("scaled Screens too!")
	-- Of course, nothing will happen if you selected an Entry because of
	-- no 'func'-Parameter; it's just a "Mouse works in scaled Res"-Showcase^^
end

function love.update(dt)
    mainmenu:update()
end

function love.draw()
    -- Scale Resolution
    love.graphics.scale(love.graphics.getWidth() / INTERNAL_RES.x, love.graphics.getHeight() / INTERNAL_RES.y)

    love.graphics.clear()
    mainmenu:draw()
end

function love.keypressed(key, scancode, isrepeat)
    menuengine.keypressed(scancode)

    if scancode == "escape" then
        love.event.quit()
    end
end

function love.mousemoved(x, y, dx, dy, istouch)
    -- Scale Mouse Position
    x = x * (INTERNAL_RES.x / love.graphics.getWidth() )
    y = y * (INTERNAL_RES.y / love.graphics.getHeight() )
    menuengine.mousemoved(x, y)
end

Known Issues

  • It's possible to show more than one Menu at the same time (that's a good thing), but unfortunately it is not possible to change Keyboard-Layout for each Menu, means that every active Menu will react if a defined Key is pressed. The only way to prevent this is to not implement "menuengine.keypressed" in "love.keypressed".