input, adding back examples

This commit is contained in:
Michael Freno
2025-10-23 09:54:17 -04:00
parent 971e5cb9d8
commit f963fc4540
17 changed files with 4381 additions and 4 deletions

View File

@@ -329,6 +329,7 @@ local Gui = {
scaleFactors = { x = 1.0, y = 1.0 }, scaleFactors = { x = 1.0, y = 1.0 },
defaultTheme = nil, defaultTheme = nil,
_cachedViewport = { width = 0, height = 0 }, -- Cached viewport dimensions _cachedViewport = { width = 0, height = 0 }, -- Cached viewport dimensions
_focusedElement = nil, -- Currently focused element for keyboard input
} }
-- ==================== -- ====================
@@ -2207,6 +2208,24 @@ function Gui.update(dt)
Gui._activeEventElement = nil Gui._activeEventElement = nil
end end
--- Forward text input to focused element
---@param text string -- Character input
function Gui.textinput(text)
if Gui._focusedElement then
Gui._focusedElement:textinput(text)
end
end
--- Forward key press to focused element
---@param key string -- Key name
---@param scancode string -- Scancode
---@param isrepeat boolean -- Whether this is a key repeat
function Gui.keypressed(key, scancode, isrepeat)
if Gui._focusedElement then
Gui._focusedElement:keypressed(key, scancode, isrepeat)
end
end
--- Destroy all elements and their children --- Destroy all elements and their children
function Gui.destroy() function Gui.destroy()
for _, win in ipairs(Gui.topElements) do for _, win in ipairs(Gui.topElements) do
@@ -2222,6 +2241,8 @@ function Gui.destroy()
Gui._gameCanvas = nil Gui._gameCanvas = nil
Gui._backdropCanvas = nil Gui._backdropCanvas = nil
Gui._canvasDimensions = { width = 0, height = 0 } Gui._canvasDimensions = { width = 0, height = 0 }
-- Clear focused element
Gui._focusedElement = nil
end end
-- Simple GUI library for LOVE2D -- Simple GUI library for LOVE2D
@@ -2639,6 +2660,34 @@ Public API methods to access internal state:
---@field contentBlur {intensity:number, quality:number}? -- Blur the element's content including children (intensity: 0-100, quality: 1-10) ---@field contentBlur {intensity:number, quality:number}? -- Blur the element's content including children (intensity: 0-100, quality: 1-10)
---@field backdropBlur {intensity:number, quality:number}? -- Blur content behind the element (intensity: 0-100, quality: 1-10) ---@field backdropBlur {intensity:number, quality:number}? -- Blur content behind the element (intensity: 0-100, quality: 1-10)
---@field _blurInstance table? -- Internal: cached blur effect instance ---@field _blurInstance table? -- Internal: cached blur effect instance
---@field editable boolean -- Whether the element is editable (default: false)
---@field multiline boolean -- Whether the element supports multiple lines (default: false)
---@field textWrap boolean|"word"|"char" -- Text wrapping mode (default: false for single-line, "word" for multi-line)
---@field maxLines number? -- Maximum number of lines (default: nil)
---@field maxLength number? -- Maximum text length in characters (default: nil)
---@field placeholder string? -- Placeholder text when empty (default: nil)
---@field passwordMode boolean -- Whether to display text as password (default: false)
---@field inputType "text"|"number"|"email"|"url" -- Input type for validation (default: "text")
---@field textOverflow "clip"|"ellipsis"|"scroll" -- Text overflow behavior (default: "clip")
---@field scrollable boolean -- Whether text is scrollable (default: false for single-line, true for multi-line)
---@field autoGrow boolean -- Whether element auto-grows with text (default: false)
---@field selectOnFocus boolean -- Whether to select all text on focus (default: false)
---@field cursorColor Color? -- Cursor color (default: nil, uses textColor)
---@field selectionColor Color? -- Selection background color (default: nil, uses theme or default)
---@field cursorBlinkRate number -- Cursor blink rate in seconds (default: 0.5)
---@field _cursorPosition number? -- Internal: cursor character position (0-based)
---@field _cursorLine number? -- Internal: cursor line number (1-based)
---@field _cursorColumn number? -- Internal: cursor column within line
---@field _cursorBlinkTimer number? -- Internal: cursor blink timer
---@field _cursorVisible boolean? -- Internal: cursor visibility state
---@field _selectionStart number? -- Internal: selection start position
---@field _selectionEnd number? -- Internal: selection end position
---@field _selectionAnchor number? -- Internal: selection anchor point
---@field _focused boolean? -- Internal: focus state
---@field _textBuffer string? -- Internal: text buffer for editable elements
---@field _lines table? -- Internal: split lines for multi-line text
---@field _wrappedLines table? -- Internal: wrapped line data
---@field _textDirty boolean? -- Internal: flag to recalculate lines/wrapping
local Element = {} local Element = {}
Element.__index = Element Element.__index = Element
@@ -2696,6 +2745,21 @@ Element.__index = Element
---@field scalingAlgorithm "nearest"|"bilinear"? -- Scaling algorithm for 9-slice corners: "nearest" (sharp/pixelated) or "bilinear" (smooth) (overrides theme setting) ---@field scalingAlgorithm "nearest"|"bilinear"? -- Scaling algorithm for 9-slice corners: "nearest" (sharp/pixelated) or "bilinear" (smooth) (overrides theme setting)
---@field contentBlur {intensity:number, quality:number}? -- Blur the element's content including children (intensity: 0-100, quality: 1-10, default: nil) ---@field contentBlur {intensity:number, quality:number}? -- Blur the element's content including children (intensity: 0-100, quality: 1-10, default: nil)
---@field backdropBlur {intensity:number, quality:number}? -- Blur content behind the element (intensity: 0-100, quality: 1-10, default: nil) ---@field backdropBlur {intensity:number, quality:number}? -- Blur content behind the element (intensity: 0-100, quality: 1-10, default: nil)
---@field editable boolean? -- Whether the element is editable (default: false)
---@field multiline boolean? -- Whether the element supports multiple lines (default: false)
---@field textWrap boolean|"word"|"char"? -- Text wrapping mode (default: false for single-line, "word" for multi-line)
---@field maxLines number? -- Maximum number of lines (default: nil)
---@field maxLength number? -- Maximum text length in characters (default: nil)
---@field placeholder string? -- Placeholder text when empty (default: nil)
---@field passwordMode boolean? -- Whether to display text as password (default: false)
---@field inputType "text"|"number"|"email"|"url"? -- Input type for validation (default: "text")
---@field textOverflow "clip"|"ellipsis"|"scroll"? -- Text overflow behavior (default: "clip")
---@field scrollable boolean? -- Whether text is scrollable (default: false for single-line, true for multi-line)
---@field autoGrow boolean? -- Whether element auto-grows with text (default: false)
---@field selectOnFocus boolean? -- Whether to select all text on focus (default: false)
---@field cursorColor Color? -- Cursor color (default: nil, uses textColor)
---@field selectionColor Color? -- Selection background color (default: nil, uses theme or default)
---@field cursorBlinkRate number? -- Cursor blink rate in seconds (default: 0.5)
local ElementProps = {} local ElementProps = {}
---@param props ElementProps ---@param props ElementProps
@@ -2706,6 +2770,13 @@ function Element.new(props)
self.callback = props.callback self.callback = props.callback
self.id = props.id or "" self.id = props.id or ""
-- Input event callbacks
self.onFocus = props.onFocus
self.onBlur = props.onBlur
self.onTextInput = props.onTextInput
self.onTextChange = props.onTextChange
self.onEnter = props.onEnter
-- Initialize click tracking for event system -- Initialize click tracking for event system
self._pressed = {} -- Track pressed state per mouse button self._pressed = {} -- Track pressed state per mouse button
self._lastClickTime = nil self._lastClickTime = nil
@@ -2775,6 +2846,63 @@ function Element.new(props)
self.backdropBlur = props.backdropBlur self.backdropBlur = props.backdropBlur
self._blurInstance = nil self._blurInstance = nil
-- Initialize input control properties
self.editable = props.editable or false
self.multiline = props.multiline or false
self.passwordMode = props.passwordMode or false
-- Validate property combinations: passwordMode disables multiline
if self.passwordMode then
self.multiline = false
end
self.textWrap = props.textWrap
if self.textWrap == nil then
self.textWrap = self.multiline and "word" or false
end
self.maxLines = props.maxLines
self.maxLength = props.maxLength
self.placeholder = props.placeholder
self.inputType = props.inputType or "text"
-- Text behavior properties
self.textOverflow = props.textOverflow or "clip"
self.scrollable = props.scrollable
if self.scrollable == nil then
self.scrollable = self.multiline
end
self.autoGrow = props.autoGrow or false
self.selectOnFocus = props.selectOnFocus or false
-- Cursor and selection properties
self.cursorColor = props.cursorColor
self.selectionColor = props.selectionColor
self.cursorBlinkRate = props.cursorBlinkRate or 0.5
-- Initialize cursor and selection state (only if editable)
if self.editable then
self._cursorPosition = 0 -- Character index (0 = before first char)
self._cursorLine = 1 -- Current line number (1-based)
self._cursorColumn = 0 -- Column within current line
self._cursorBlinkTimer = 0
self._cursorVisible = true
-- Selection state
self._selectionStart = nil -- nil = no selection
self._selectionEnd = nil
self._selectionAnchor = nil -- Anchor point for shift+arrow selection
-- Focus state
self._focused = false
-- Text buffer state (initialized after self.text is set below)
self._textBuffer = props.text or "" -- Actual text content
self._lines = nil -- Split lines (for multiline)
self._wrappedLines = nil -- Wrapped line data
self._textDirty = true -- Flag to recalculate lines/wrapping
end
-- Set parent first so it's available for size calculations -- Set parent first so it's available for size calculations
self.parent = props.parent self.parent = props.parent
@@ -4553,6 +4681,15 @@ function Element:update(dt)
child:update(dt) child:update(dt)
end end
-- Update cursor blink timer (only if editable and focused)
if self.editable and self._focused then
self._cursorBlinkTimer = self._cursorBlinkTimer + dt
if self._cursorBlinkTimer >= self.cursorBlinkRate then
self._cursorBlinkTimer = 0
self._cursorVisible = not self._cursorVisible
end
end
-- Update animation if exists -- Update animation if exists
if self.animation then if self.animation then
local finished = self.animation:update(dt) local finished = self.animation:update(dt)
@@ -4678,6 +4815,11 @@ function Element:update(dt)
self.callback(self, clickEvent) self.callback(self, clickEvent)
self._pressed[button] = false self._pressed[button] = false
-- Focus editable elements on left click
if button == 1 and self.editable then
self:focus()
end
-- Fire release event -- Fire release event
local releaseEvent = InputEvent.new({ local releaseEvent = InputEvent.new({
type = "release", type = "release",
@@ -5268,6 +5410,639 @@ function Element:updateOpacity(newOpacity)
end end
end end
-- ====================
-- Input Handling - Cursor Management
-- ====================
--- Set cursor position
---@param position number -- Character index (0-based)
function Element:setCursorPosition(position)
if not self.editable then return end
self._cursorPosition = position
self:_validateCursorPosition()
self:_resetCursorBlink()
end
--- Get cursor position
---@return number -- Character index (0-based)
function Element:getCursorPosition()
if not self.editable then return 0 end
return self._cursorPosition
end
--- Move cursor by delta characters
---@param delta number -- Number of characters to move (positive or negative)
function Element:moveCursorBy(delta)
if not self.editable then return end
self._cursorPosition = self._cursorPosition + delta
self:_validateCursorPosition()
self:_resetCursorBlink()
end
--- Move cursor to start of text
function Element:moveCursorToStart()
if not self.editable then return end
self._cursorPosition = 0
self:_resetCursorBlink()
end
--- Move cursor to end of text
function Element:moveCursorToEnd()
if not self.editable then return end
local textLength = utf8.len(self._textBuffer or "")
self._cursorPosition = textLength
self:_resetCursorBlink()
end
--- Move cursor to start of current line
function Element:moveCursorToLineStart()
if not self.editable then return end
-- For now, just move to start (will be enhanced for multi-line)
self:moveCursorToStart()
end
--- Move cursor to end of current line
function Element:moveCursorToLineEnd()
if not self.editable then return end
-- For now, just move to end (will be enhanced for multi-line)
self:moveCursorToEnd()
end
--- Validate cursor position (ensure it's within text bounds)
function Element:_validateCursorPosition()
if not self.editable then return end
local textLength = utf8.len(self._textBuffer or "")
self._cursorPosition = math.max(0, math.min(self._cursorPosition, textLength))
end
--- Reset cursor blink (show cursor immediately)
function Element:_resetCursorBlink()
if not self.editable then return end
self._cursorBlinkTimer = 0
self._cursorVisible = true
end
-- ====================
-- Input Handling - Selection Management
-- ====================
--- Set selection range
---@param startPos number -- Start position (inclusive)
---@param endPos number -- End position (inclusive)
function Element:setSelection(startPos, endPos)
if not self.editable then return end
local textLength = utf8.len(self._textBuffer or "")
self._selectionStart = math.max(0, math.min(startPos, textLength))
self._selectionEnd = math.max(0, math.min(endPos, textLength))
-- Ensure start <= end
if self._selectionStart > self._selectionEnd then
self._selectionStart, self._selectionEnd = self._selectionEnd, self._selectionStart
end
self:_resetCursorBlink()
end
--- Get selection range
---@return number?, number? -- Start and end positions, or nil if no selection
function Element:getSelection()
if not self.editable then return nil, nil end
if not self:hasSelection() then return nil, nil end
return self._selectionStart, self._selectionEnd
end
--- Check if there is an active selection
---@return boolean
function Element:hasSelection()
if not self.editable then return false end
return self._selectionStart ~= nil and self._selectionEnd ~= nil and self._selectionStart ~= self._selectionEnd
end
--- Clear selection
function Element:clearSelection()
if not self.editable then return end
self._selectionStart = nil
self._selectionEnd = nil
self._selectionAnchor = nil
end
--- Select all text
function Element:selectAll()
if not self.editable then return end
local textLength = utf8.len(self._textBuffer or "")
self._selectionStart = 0
self._selectionEnd = textLength
self:_resetCursorBlink()
end
--- Get selected text
---@return string? -- Selected text or nil if no selection
function Element:getSelectedText()
if not self.editable or not self:hasSelection() then return nil end
local startPos, endPos = self:getSelection()
if not startPos or not endPos then return nil end
-- Convert character indices to byte offsets for utf8.sub
local text = self._textBuffer or ""
return utf8.sub(text, startPos + 1, endPos)
end
--- Delete selected text
---@return boolean -- True if text was deleted
function Element:deleteSelection()
if not self.editable or not self:hasSelection() then return false end
local startPos, endPos = self:getSelection()
if not startPos or not endPos then return false end
self:deleteText(startPos, endPos)
self:clearSelection()
self._cursorPosition = startPos
self:_validateCursorPosition()
return true
end
-- ====================
-- Input Handling - Focus Management
-- ====================
--- Focus this element for keyboard input
function Element:focus()
if not self.editable then return end
-- Blur previously focused element
if Gui._focusedElement and Gui._focusedElement ~= self then
Gui._focusedElement:blur()
end
-- Set focus state
self._focused = true
Gui._focusedElement = self
-- Reset cursor blink
self:_resetCursorBlink()
-- Select all text if selectOnFocus is enabled
if self.selectOnFocus then
self:selectAll()
else
-- Move cursor to end of text
self:moveCursorToEnd()
end
-- Trigger onFocus callback if defined
if self.onFocus then
self.onFocus(self)
end
end
--- Remove focus from this element
function Element:blur()
if not self.editable then return end
self._focused = false
-- Clear global focused element if it's this element
if Gui._focusedElement == self then
Gui._focusedElement = nil
end
-- Trigger onBlur callback if defined
if self.onBlur then
self.onBlur(self)
end
end
--- Check if this element is focused
---@return boolean
function Element:isFocused()
if not self.editable then return false end
return self._focused == true
end
-- ====================
-- Input Handling - Text Buffer Management
-- ====================
--- Get current text buffer
---@return string
function Element:getText()
if not self.editable then return self.text or "" end
return self._textBuffer or ""
end
--- Set text buffer and mark dirty
---@param text string
function Element:setText(text)
if not self.editable then
self.text = text
return
end
self._textBuffer = text or ""
self.text = self._textBuffer -- Sync display text
self:_markTextDirty()
self:_validateCursorPosition()
end
--- Insert text at position
---@param text string -- Text to insert
---@param position number? -- Position to insert at (default: cursor position)
function Element:insertText(text, position)
if not self.editable then return end
position = position or self._cursorPosition
local buffer = self._textBuffer or ""
-- Convert character position to byte offset
local byteOffset = utf8.offset(buffer, position + 1) or (#buffer + 1)
-- Insert text
local before = buffer:sub(1, byteOffset - 1)
local after = buffer:sub(byteOffset)
self._textBuffer = before .. text .. after
self.text = self._textBuffer -- Sync display text
-- Update cursor position
self._cursorPosition = position + utf8.len(text)
self:_markTextDirty()
self:_validateCursorPosition()
end
--- Delete text in range
---@param startPos number -- Start position (inclusive)
---@param endPos number -- End position (inclusive)
function Element:deleteText(startPos, endPos)
if not self.editable then return end
local buffer = self._textBuffer or ""
-- Ensure valid range
local textLength = utf8.len(buffer)
startPos = math.max(0, math.min(startPos, textLength))
endPos = math.max(0, math.min(endPos, textLength))
if startPos > endPos then
startPos, endPos = endPos, startPos
end
-- Convert character positions to byte offsets
local startByte = utf8.offset(buffer, startPos + 1) or 1
local endByte = utf8.offset(buffer, endPos + 1) or (#buffer + 1)
-- Delete text
local before = buffer:sub(1, startByte - 1)
local after = buffer:sub(endByte)
self._textBuffer = before .. after
self.text = self._textBuffer -- Sync display text
self:_markTextDirty()
end
--- Replace text in range
---@param startPos number -- Start position (inclusive)
---@param endPos number -- End position (inclusive)
---@param newText string -- Replacement text
function Element:replaceText(startPos, endPos, newText)
if not self.editable then return end
self:deleteText(startPos, endPos)
self:insertText(newText, startPos)
end
--- Mark text as dirty (needs recalculation)
function Element:_markTextDirty()
if not self.editable then return end
self._textDirty = true
end
--- Update text if dirty (recalculate lines and wrapping)
function Element:_updateTextIfDirty()
if not self.editable or not self._textDirty then return end
self:_splitLines()
self:_calculateWrapping()
self:_validateCursorPosition()
self._textDirty = false
end
--- Split text into lines (for multi-line text)
function Element:_splitLines()
if not self.editable then return end
if not self.multiline then
self._lines = {self._textBuffer or ""}
return
end
self._lines = {}
local text = self._textBuffer or ""
-- Split on newlines
for line in (text .. "\n"):gmatch("([^\n]*)\n") do
table.insert(self._lines, line)
end
-- Ensure at least one line
if #self._lines == 0 then
self._lines = {""}
end
end
--- Calculate text wrapping
function Element:_calculateWrapping()
if not self.editable or not self.textWrap then
self._wrappedLines = nil
return
end
self._wrappedLines = {}
local availableWidth = self.width - self.padding.left - self.padding.right
for lineNum, line in ipairs(self._lines or {}) do
if line == "" then
table.insert(self._wrappedLines, {
text = "",
startIdx = 0,
endIdx = 0,
lineNum = lineNum
})
else
local wrappedParts = self:_wrapLine(line, availableWidth)
for _, part in ipairs(wrappedParts) do
part.lineNum = lineNum
table.insert(self._wrappedLines, part)
end
end
end
end
--- Wrap a single line of text
---@param line string -- Line to wrap
---@param maxWidth number -- Maximum width in pixels
---@return table -- Array of wrapped line parts
function Element:_wrapLine(line, maxWidth)
if not self.editable then return {{text = line, startIdx = 0, endIdx = utf8.len(line)}} end
local font = self:_getFont()
local wrappedParts = {}
local currentLine = ""
local startIdx = 0
if self.textWrap == "word" then
-- Word wrapping
local words = {}
for word in line:gmatch("%S+") do
table.insert(words, word)
end
for i, word in ipairs(words) do
local testLine = currentLine == "" and word or (currentLine .. " " .. word)
local width = font:getWidth(testLine)
if width > maxWidth and currentLine ~= "" then
-- Current line is full, start new line
table.insert(wrappedParts, {
text = currentLine,
startIdx = startIdx,
endIdx = startIdx + utf8.len(currentLine)
})
currentLine = word
startIdx = startIdx + utf8.len(currentLine) + 1
else
currentLine = testLine
end
end
else
-- Character wrapping
local lineLength = utf8.len(line)
for i = 1, lineLength do
local char = utf8.sub(line, i, i)
local testLine = currentLine .. char
local width = font:getWidth(testLine)
if width > maxWidth and currentLine ~= "" then
table.insert(wrappedParts, {
text = currentLine,
startIdx = startIdx,
endIdx = startIdx + utf8.len(currentLine)
})
currentLine = char
startIdx = i - 1
else
currentLine = testLine
end
end
end
-- Add remaining text
if currentLine ~= "" then
table.insert(wrappedParts, {
text = currentLine,
startIdx = startIdx,
endIdx = startIdx + utf8.len(currentLine)
})
end
-- Ensure at least one part
if #wrappedParts == 0 then
table.insert(wrappedParts, {
text = "",
startIdx = 0,
endIdx = 0
})
end
return wrappedParts
end
--- Get font for text rendering
---@return love.Font
function Element:_getFont()
-- Get font path from theme or element
local fontPath = nil
if self.fontFamily then
local themeToUse = self.theme and themes[self.theme] or Theme.getActive()
if themeToUse and themeToUse.fonts and themeToUse.fonts[self.fontFamily] then
fontPath = themeToUse.fonts[self.fontFamily]
else
-- Assume fontFamily is a direct path
fontPath = self.fontFamily
end
end
return FONT_CACHE.getFont(self.textSize, fontPath)
end
-- ====================
-- Input Handling - Keyboard Input
-- ====================
--- Handle text input (character input)
---@param text string -- Character(s) to insert
function Element:textinput(text)
if not self.editable or not self._focused then return end
-- Trigger onTextInput callback if defined
if self.onTextInput then
local result = self.onTextInput(self, text)
-- If callback returns false, cancel the input
if result == false then return end
end
-- Capture old text for callback
local oldText = self._textBuffer
-- Delete selection if exists
local hadSelection = self:hasSelection()
if hadSelection then
self:deleteSelection()
end
-- Insert text at cursor position
self:insertText(text)
-- Trigger onTextChange callback if text changed
if self.onTextChange and self._textBuffer ~= oldText then
self.onTextChange(self, self._textBuffer, oldText)
end
end
--- Handle key press (special keys)
---@param key string -- Key name
---@param scancode string -- Scancode
---@param isrepeat boolean -- Whether this is a key repeat
function Element:keypressed(key, scancode, isrepeat)
if not self.editable or not self._focused then return end
local modifiers = getModifiers()
local ctrl = modifiers.ctrl or modifiers.super -- Support both Ctrl and Cmd
-- Handle cursor movement
if key == "left" then
if self:hasSelection() and not modifiers.shift then
-- Move to start of selection
local startPos, _ = self:getSelection()
self._cursorPosition = startPos
self:clearSelection()
else
self:moveCursorBy(-1)
end
self:_resetCursorBlink()
elseif key == "right" then
if self:hasSelection() and not modifiers.shift then
-- Move to end of selection
local _, endPos = self:getSelection()
self._cursorPosition = endPos
self:clearSelection()
else
self:moveCursorBy(1)
end
self:_resetCursorBlink()
elseif key == "home" or (ctrl and key == "a" and not self.multiline) then
-- Move to line start (or document start for single-line)
if ctrl or not self.multiline then
self:moveCursorToStart()
else
self:moveCursorToLineStart()
end
if key == "home" then
self:clearSelection()
end
self:_resetCursorBlink()
elseif key == "end" or (ctrl and key == "e" and not self.multiline) then
-- Move to line end (or document end for single-line)
if ctrl or not self.multiline then
self:moveCursorToEnd()
else
self:moveCursorToLineEnd()
end
if key == "end" then
self:clearSelection()
end
self:_resetCursorBlink()
-- Handle backspace and delete
elseif key == "backspace" then
local oldText = self._textBuffer
if self:hasSelection() then
-- Delete selection
self:deleteSelection()
elseif self._cursorPosition > 0 then
-- Delete character before cursor
self:deleteText(self._cursorPosition - 1, self._cursorPosition)
self._cursorPosition = self._cursorPosition - 1
self:_validateCursorPosition()
end
-- Trigger onTextChange callback
if self.onTextChange and self._textBuffer ~= oldText then
self.onTextChange(self, self._textBuffer, oldText)
end
self:_resetCursorBlink()
elseif key == "delete" then
local oldText = self._textBuffer
if self:hasSelection() then
-- Delete selection
self:deleteSelection()
else
-- Delete character after cursor
local textLength = utf8.len(self._textBuffer or "")
if self._cursorPosition < textLength then
self:deleteText(self._cursorPosition, self._cursorPosition + 1)
end
end
-- Trigger onTextChange callback
if self.onTextChange and self._textBuffer ~= oldText then
self.onTextChange(self, self._textBuffer, oldText)
end
self:_resetCursorBlink()
-- Handle return/enter
elseif key == "return" or key == "kpenter" then
if self.multiline then
-- Insert newline
local oldText = self._textBuffer
if self:hasSelection() then
self:deleteSelection()
end
self:insertText("\n")
-- Trigger onTextChange callback
if self.onTextChange and self._textBuffer ~= oldText then
self.onTextChange(self, self._textBuffer, oldText)
end
else
-- Trigger onEnter callback for single-line
if self.onEnter then
self.onEnter(self)
end
end
self:_resetCursorBlink()
-- Handle Ctrl/Cmd+A (select all)
elseif ctrl and key == "a" then
self:selectAll()
self:_resetCursorBlink()
-- Handle Escape
elseif key == "escape" then
if self:hasSelection() then
-- Clear selection
self:clearSelection()
else
-- Blur element
self:blur()
end
self:_resetCursorBlink()
end
end
Gui.new = Element.new Gui.new = Element.new
Gui.Element = Element Gui.Element = Element
Gui.Animation = Animation Gui.Animation = Animation

View File

@@ -0,0 +1,219 @@
--[[
FlexLove Example 01: Flex Positioning
This example demonstrates flexbox layouts in FlexLove:
- Flex direction (horizontal/vertical)
- Justify content (main axis alignment)
- Align items (cross axis alignment)
- Flex wrap behavior
Run with: love /path/to/libs/examples/01_flex_positioning.lua
]]
-- Map Love to Lv to avoid duplicate definitions
local Lv = love
-- Load FlexLove from parent directory
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
function Lv.load()
-- Initialize FlexLove with base scaling
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "6vh",
text = "FlexLove Example 01: Flex Positioning",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 1: Horizontal Flex with Different JustifyContent Values
-- ========================================
local yOffset = 10
-- Label for justify-content section
Gui.new({
x = "2vw",
y = yOffset .. "vh",
width = "96vw",
height = "3vh",
text = "Horizontal Flex - JustifyContent Options",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
yOffset = yOffset + 4
-- Demonstrate each justify-content option
local justifyOptions = {
{ name = "flex-start", value = enums.JustifyContent.FLEX_START },
{ name = "center", value = enums.JustifyContent.CENTER },
{ name = "flex-end", value = enums.JustifyContent.FLEX_END },
{ name = "space-between", value = enums.JustifyContent.SPACE_BETWEEN },
{ name = "space-around", value = enums.JustifyContent.SPACE_AROUND },
{ name = "space-evenly", value = enums.JustifyContent.SPACE_EVENLY },
}
for _, option in ipairs(justifyOptions) do
-- Label for this justify option
Gui.new({
x = "2vw",
y = yOffset .. "vh",
width = "15vw",
height = "3vh",
text = option.name,
textSize = "1.8vh",
textColor = Color.new(0.8, 0.8, 1, 1),
textAlign = enums.TextAlign.START,
})
-- Container demonstrating this justify-content value
local container = Gui.new({
x = "18vw",
y = yOffset .. "vh",
width = "78vw",
height = "8vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.HORIZONTAL,
justifyContent = option.value,
alignItems = enums.AlignItems.CENTER,
backgroundColor = Color.new(0.15, 0.15, 0.2, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.3, 0.4, 1),
})
-- Add child elements
local colors = {
Color.new(0.8, 0.3, 0.3, 1),
Color.new(0.3, 0.8, 0.3, 1),
Color.new(0.3, 0.3, 0.8, 1),
Color.new(0.8, 0.8, 0.3, 1),
}
for j = 1, 4 do
Gui.new({
parent = container,
width = "8vw",
height = "5vh",
backgroundColor = colors[j],
text = tostring(j),
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
yOffset = yOffset + 9
end
-- ========================================
-- Section 2: Vertical Flex with Different AlignItems Values
-- ========================================
yOffset = yOffset + 2
-- Label for align-items section
Gui.new({
x = "2vw",
y = yOffset .. "vh",
width = "96vw",
height = "3vh",
text = "Vertical Flex - AlignItems Options",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
yOffset = yOffset + 4
-- Note: Due to space constraints, we'll show a subset in a horizontal layout
local alignOptions = {
{ name = "stretch", value = enums.AlignItems.STRETCH },
{ name = "flex-start", value = enums.AlignItems.FLEX_START },
{ name = "center", value = enums.AlignItems.CENTER },
{ name = "flex-end", value = enums.AlignItems.FLEX_END },
}
local xOffset = 2
local containerWidth = 22
for _, option in ipairs(alignOptions) do
-- Label for this align option
Gui.new({
x = xOffset .. "vw",
y = yOffset .. "vh",
width = containerWidth .. "vw",
height = "2.5vh",
text = option.name,
textSize = "1.8vh",
textColor = Color.new(0.8, 1, 0.8, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Container demonstrating this align-items value
local container = Gui.new({
x = xOffset .. "vw",
y = (yOffset + 3) .. "vh",
width = containerWidth .. "vw",
height = "20vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.VERTICAL,
justifyContent = enums.JustifyContent.FLEX_START,
alignItems = option.value,
gap = 5,
backgroundColor = Color.new(0.15, 0.2, 0.15, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.4, 0.3, 1),
})
-- Add child elements with varying widths
local widths = { "8vw", "12vw", "6vw" }
local colors = {
Color.new(0.9, 0.4, 0.4, 1),
Color.new(0.4, 0.9, 0.4, 1),
Color.new(0.4, 0.4, 0.9, 1),
}
for j = 1, 3 do
Gui.new({
parent = container,
width = option.value == enums.AlignItems.STRETCH and "auto" or widths[j],
height = "4vh",
backgroundColor = colors[j],
text = tostring(j),
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
xOffset = xOffset + containerWidth + 2
end
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
-- Dark background
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end
-- Note: Mouse event handlers would be added here if needed for interactivity

229
examples/02_grid_layout.lua Normal file
View File

@@ -0,0 +1,229 @@
--[[
FlexLove Example 02: Grid Layout
This example demonstrates grid layouts in FlexLove:
- Different grid configurations (2x2, 3x3, 4x2)
- Row and column gaps
- AlignItems behavior in grid cells
- Automatic grid cell positioning
Run with: love /path/to/libs/examples/02_grid_layout.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "6vh",
text = "FlexLove Example 02: Grid Layout",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 1: 2x2 Grid with Gaps
-- ========================================
Gui.new({
x = "2vw",
y = "10vh",
width = "30vw",
height = "3vh",
text = "2x2 Grid (rowGap: 10px, columnGap: 10px)",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local grid2x2 = Gui.new({
x = "2vw",
y = "14vh",
width = "30vw",
height = "30vh",
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
rowGap = 10,
columnGap = 10,
backgroundColor = Color.new(0.1, 0.1, 0.15, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.3, 0.4, 1),
})
-- Add 4 cells to 2x2 grid
local colors2x2 = {
Color.new(0.8, 0.3, 0.3, 1),
Color.new(0.3, 0.8, 0.3, 1),
Color.new(0.3, 0.3, 0.8, 1),
Color.new(0.8, 0.8, 0.3, 1),
}
for j = 1, 4 do
Gui.new({
parent = grid2x2,
backgroundColor = colors2x2[j],
text = "Cell " .. j,
textSize = "2.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- ========================================
-- Section 2: 3x3 Grid with Different Gap Sizes
-- ========================================
Gui.new({
x = "34vw",
y = "10vh",
width = "30vw",
height = "3vh",
text = "3x3 Grid (rowGap: 5px, columnGap: 15px)",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local grid3x3 = Gui.new({
x = "34vw",
y = "14vh",
width = "30vw",
height = "30vh",
positioning = enums.Positioning.GRID,
gridRows = 3,
gridColumns = 3,
rowGap = 5,
columnGap = 15,
backgroundColor = Color.new(0.1, 0.15, 0.1, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.4, 0.3, 1),
})
-- Add 9 cells to 3x3 grid
for j = 1, 9 do
local hue = (j - 1) / 9
Gui.new({
parent = grid3x3,
backgroundColor = Color.new(0.3 + hue * 0.5, 0.5, 0.7 - hue * 0.4, 1),
text = tostring(j),
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- ========================================
-- Section 3: 4x2 Grid with AlignItems
-- ========================================
Gui.new({
x = "66vw",
y = "10vh",
width = "32vw",
height = "3vh",
text = "4x2 Grid (alignItems: center)",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local grid4x2 = Gui.new({
x = "66vw",
y = "14vh",
width = "32vw",
height = "30vh",
positioning = enums.Positioning.GRID,
gridRows = 4,
gridColumns = 2,
rowGap = 8,
columnGap = 8,
alignItems = enums.AlignItems.CENTER,
backgroundColor = Color.new(0.15, 0.1, 0.15, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.4, 0.3, 0.4, 1),
})
-- Add 8 cells with varying content
for j = 1, 8 do
Gui.new({
parent = grid4x2,
backgroundColor = Color.new(0.6, 0.4 + j * 0.05, 0.7 - j * 0.05, 1),
text = "Item " .. j,
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- ========================================
-- Section 4: Grid with Responsive Units (vw/vh gaps)
-- ========================================
Gui.new({
x = "2vw",
y = "46vh",
width = "96vw",
height = "3vh",
text = "Grid with Responsive Gaps (rowGap: 2vh, columnGap: 2vw)",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local gridResponsive = Gui.new({
x = "2vw",
y = "50vh",
width = "96vw",
height = "45vh",
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 5,
rowGap = "2vh",
columnGap = "2vw",
backgroundColor = Color.new(0.08, 0.08, 0.12, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.25, 0.25, 0.35, 1),
})
-- Add 10 cells with gradient colors
for j = 1, 10 do
local progress = (j - 1) / 9
Gui.new({
parent = gridResponsive,
backgroundColor = Color.new(
0.2 + progress * 0.6,
0.4 + math.sin(progress * 3.14) * 0.4,
0.8 - progress * 0.4,
1
),
text = "Cell " .. j,
textSize = "2.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
end
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -0,0 +1,301 @@
--[[
FlexLove Example 03: Theming System
This example demonstrates the theming system in FlexLove:
- Loading and applying themes
- Theme components (button, panel, card)
- Theme states (normal, hover, pressed, disabled, active)
- Theme colors and fonts
Run with: love /path/to/libs/examples/03_theming_system.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Load the space theme
Gui.loadTheme("space", "../themes/space")
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "6vh",
text = "FlexLove Example 03: Theming System",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 1: Theme Components
-- ========================================
Gui.new({
x = "2vw",
y = "10vh",
width = "96vw",
height = "3vh",
text = "Theme Components - Space Theme",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Card component
Gui.new({
x = "2vw",
y = "14vh",
width = "30vw",
height = "20vh",
theme = "space",
themeComponent = "card",
text = "Card Component",
textSize = "2.5vh",
textColor = Color.new(0.8, 0.9, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Panel component
Gui.new({
x = "34vw",
y = "14vh",
width = "30vw",
height = "20vh",
theme = "space",
themeComponent = "panel",
text = "Panel Component",
textSize = "2.5vh",
textColor = Color.new(0.8, 0.9, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Panel red component
Gui.new({
x = "66vw",
y = "14vh",
width = "32vw",
height = "20vh",
theme = "space",
themeComponent = "panelred",
text = "Panel Red Component",
textSize = "2.5vh",
textColor = Color.new(1, 0.8, 0.8, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 2: Button States
-- ========================================
Gui.new({
x = "2vw",
y = "36vh",
width = "96vw",
height = "3vh",
text = "Button States - Hover and Click to See State Changes",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Normal button (hover to see hover state, click to see pressed state)
Gui.new({
x = "2vw",
y = "40vh",
width = "22vw",
height = "8vh",
theme = "space",
themeComponent = "button",
text = "Normal Button",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
callback = function(element, event)
if event.type == "click" then
print("Normal button clicked!")
end
end,
})
-- Active button (simulating active state)
local activeButton = Gui.new({
x = "26vw",
y = "40vh",
width = "22vw",
height = "8vh",
theme = "space",
themeComponent = "button",
text = "Active Button",
textSize = "2vh",
textColor = Color.new(0.3, 1, 0.3, 1),
textAlign = enums.TextAlign.CENTER,
active = true,
callback = function(element, event)
if event.type == "click" then
element.active = not element.active
print("Active button toggled:", element.active)
end
end,
})
-- Disabled button
Gui.new({
x = "50vw",
y = "40vh",
width = "22vw",
height = "8vh",
theme = "space",
themeComponent = "button",
text = "Disabled Button",
textSize = "2vh",
textColor = Color.new(0.5, 0.5, 0.5, 1),
textAlign = enums.TextAlign.CENTER,
disabled = true,
callback = function(element, event)
-- This won't be called because button is disabled
print("This shouldn't print!")
end,
})
-- Button with callback feedback
local clickCount = 0
local counterButton = Gui.new({
x = "74vw",
y = "40vh",
width = "24vw",
height = "8vh",
theme = "space",
themeComponent = "button",
text = "Click Me! (0)",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
callback = function(element, event)
if event.type == "click" then
clickCount = clickCount + 1
element.text = "Click Me! (" .. clickCount .. ")"
end
end,
})
-- ========================================
-- Section 3: Theme Colors and Fonts
-- ========================================
Gui.new({
x = "2vw",
y = "50vh",
width = "96vw",
height = "3vh",
text = "Theme Colors and Fonts",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Container showing theme colors
local colorContainer = Gui.new({
x = "2vw",
y = "54vh",
width = "96vw",
height = "20vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.HORIZONTAL,
justifyContent = enums.JustifyContent.SPACE_EVENLY,
alignItems = enums.AlignItems.CENTER,
backgroundColor = Color.new(0.1, 0.1, 0.15, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.3, 0.4, 1),
})
-- Primary color swatch
Gui.new({
parent = colorContainer,
width = "20vw",
height = "15vh",
backgroundColor = Color.new(0.08, 0.75, 0.95, 1), -- Theme primary color
text = "Primary Color",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
-- Secondary color swatch
Gui.new({
parent = colorContainer,
width = "20vw",
height = "15vh",
backgroundColor = Color.new(0.15, 0.20, 0.25, 1), -- Theme secondary color
text = "Secondary Color",
textSize = "2vh",
textColor = Color.new(0.8, 0.9, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
-- Text color swatch
Gui.new({
parent = colorContainer,
width = "20vw",
height = "15vh",
backgroundColor = Color.new(0.2, 0.2, 0.25, 1),
text = "Text Color",
textSize = "2vh",
textColor = Color.new(0.80, 0.90, 1.00, 1), -- Theme text color
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
-- Text dark color swatch
Gui.new({
parent = colorContainer,
width = "20vw",
height = "15vh",
backgroundColor = Color.new(0.25, 0.25, 0.3, 1),
text = "Text Dark Color",
textSize = "2vh",
textColor = Color.new(0.35, 0.40, 0.45, 1), -- Theme textDark color
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
-- ========================================
-- Section 4: Font Family from Theme
-- ========================================
Gui.new({
x = "2vw",
y = "76vh",
width = "96vw",
height = "18vh",
theme = "space",
themeComponent = "card",
text = "This text uses the theme's default font (VT323)",
textSize = "3vh",
textColor = Color.new(0.8, 0.9, 1, 1),
textAlign = enums.TextAlign.CENTER,
fontFamily = "default", -- References theme font
})
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -0,0 +1,241 @@
--[[
FlexLove Example 04: Responsive Units
This example demonstrates responsive unit systems in FlexLove:
- Viewport units (vw, vh)
- Percentage units (%)
- Pixel units (px)
- How elements resize with the window
Run with: love /path/to/libs/examples/04_responsive_units.lua
Try resizing the window to see responsive behavior!
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "6vh",
text = "FlexLove Example 04: Responsive Units - Try Resizing!",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 1: Viewport Width Units (vw)
-- ========================================
Gui.new({
x = "2vw",
y = "10vh",
width = "96vw",
height = "3vh",
text = "Viewport Width (vw) - Scales with window width",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local vwContainer = Gui.new({
x = "2vw",
y = "14vh",
width = "96vw",
height = "12vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.HORIZONTAL,
justifyContent = enums.JustifyContent.SPACE_EVENLY,
alignItems = enums.AlignItems.CENTER,
backgroundColor = Color.new(0.1, 0.1, 0.15, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.3, 0.4, 1),
})
-- Elements with vw widths
local vwWidths = { "10vw", "15vw", "20vw", "25vw" }
local colors = {
Color.new(0.8, 0.3, 0.3, 1),
Color.new(0.3, 0.8, 0.3, 1),
Color.new(0.3, 0.3, 0.8, 1),
Color.new(0.8, 0.8, 0.3, 1),
}
for i, width in ipairs(vwWidths) do
Gui.new({
parent = vwContainer,
width = width,
height = "8vh",
backgroundColor = colors[i],
text = width,
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- ========================================
-- Section 2: Viewport Height Units (vh)
-- ========================================
Gui.new({
x = "2vw",
y = "28vh",
width = "96vw",
height = "3vh",
text = "Viewport Height (vh) - Scales with window height",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local vhContainer = Gui.new({
x = "2vw",
y = "32vh",
width = "96vw",
height = "30vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.HORIZONTAL,
justifyContent = enums.JustifyContent.SPACE_EVENLY,
alignItems = enums.AlignItems.FLEX_END,
backgroundColor = Color.new(0.1, 0.15, 0.1, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.4, 0.3, 1),
})
-- Elements with vh heights
local vhHeights = { "8vh", "12vh", "16vh", "20vh", "24vh" }
for i, height in ipairs(vhHeights) do
local hue = (i - 1) / 4
Gui.new({
parent = vhContainer,
width = "16vw",
height = height,
backgroundColor = Color.new(0.3 + hue * 0.5, 0.5, 0.7 - hue * 0.4, 1),
text = height,
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- ========================================
-- Section 3: Percentage Units (%)
-- ========================================
Gui.new({
x = "2vw",
y = "64vh",
width = "46vw",
height = "3vh",
text = "Percentage (%) - Relative to parent",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local percentContainer = Gui.new({
x = "2vw",
y = "68vh",
width = "46vw",
height = "28vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.VERTICAL,
justifyContent = enums.JustifyContent.SPACE_EVENLY,
alignItems = enums.AlignItems.STRETCH,
backgroundColor = Color.new(0.15, 0.1, 0.15, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.4, 0.3, 0.4, 1),
padding = { top = 10, right = 10, bottom = 10, left = 10 },
})
-- Child elements with percentage widths
local percentWidths = { "25%", "50%", "75%", "100%" }
for i, width in ipairs(percentWidths) do
local progress = (i - 1) / 3
Gui.new({
parent = percentContainer,
width = width,
height = "5vh",
backgroundColor = Color.new(0.6, 0.4 + progress * 0.4, 0.7 - progress * 0.4, 1),
text = width .. " of parent",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- ========================================
-- Section 4: Pixel Units (px) - Fixed Size
-- ========================================
Gui.new({
x = "50vw",
y = "64vh",
width = "48vw",
height = "3vh",
text = "Pixels (px) - Fixed size (doesn't resize)",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local pxContainer = Gui.new({
x = "50vw",
y = "68vh",
width = "48vw",
height = "28vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.HORIZONTAL,
justifyContent = enums.JustifyContent.CENTER,
alignItems = enums.AlignItems.CENTER,
gap = 10,
backgroundColor = Color.new(0.1, 0.12, 0.15, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.35, 0.4, 1),
})
-- Fixed pixel size elements
local pxSizes = {
{ w = 80, h = 80 },
{ w = 100, h = 100 },
{ w = 120, h = 120 },
}
for i, size in ipairs(pxSizes) do
Gui.new({
parent = pxContainer,
width = size.w,
height = size.h,
backgroundColor = Color.new(0.8 - i * 0.2, 0.3 + i * 0.2, 0.5, 1),
text = size.w .. "px",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
end
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

247
examples/05_animations.lua Normal file
View File

@@ -0,0 +1,247 @@
--[[
FlexLove Example 05: Animations
This example demonstrates animation system in FlexLove:
- Fade animations
- Scale animations
- Custom animations with different easing functions
- Animation timing and interpolation
Run with: love /path/to/libs/examples/05_animations.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local Animation = FlexLove.Animation
local enums = FlexLove.enums
-- Animation control variables
local fadeBox, scaleBox, easingBoxes
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "6vh",
text = "FlexLove Example 05: Animations",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 1: Fade Animation
-- ========================================
Gui.new({
x = "2vw",
y = "10vh",
width = "46vw",
height = "3vh",
text = "Fade Animation - Click to trigger",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
fadeBox = Gui.new({
x = "2vw",
y = "14vh",
width = "46vw",
height = "20vh",
backgroundColor = Color.new(0.3, 0.6, 0.9, 1),
text = "Click me to fade out and back in",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
callback = function(element, event)
if event.type == "click" then
-- Fade out then fade in
local fadeOut = Animation.fade(1.0, 1.0, 0.0)
fadeOut.easing = function(t) return t * t end -- easeInQuad
element.animation = fadeOut
-- Queue fade in after fade out completes
local startTime = Lv.timer.getTime()
element._fadeCallback = function(el, dt)
if Lv.timer.getTime() - startTime >= 1.0 then
local fadeIn = Animation.fade(1.0, 0.0, 1.0)
fadeIn.easing = function(t) return t * (2 - t) end -- easeOutQuad
el.animation = fadeIn
el._fadeCallback = nil
end
end
end
end,
})
-- ========================================
-- Section 2: Scale Animation
-- ========================================
Gui.new({
x = "50vw",
y = "10vh",
width = "48vw",
height = "3vh",
text = "Scale Animation - Click to trigger",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
scaleBox = Gui.new({
x = "50vw",
y = "14vh",
width = 400,
height = 200,
backgroundColor = Color.new(0.9, 0.4, 0.4, 1),
text = "Click me to scale up",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
callback = function(element, event)
if event.type == "click" then
-- Scale up
local scaleUp = Animation.scale(
0.5,
{ width = element.width, height = element.height },
{ width = element.width * 1.5, height = element.height * 1.5 }
)
scaleUp.easing = function(t) return t < 0.5 and 2 * t * t or -1 + (4 - 2 * t) * t end -- easeInOutQuad
element.animation = scaleUp
-- Queue scale down
local startTime = Lv.timer.getTime()
element._scaleCallback = function(el, dt)
if Lv.timer.getTime() - startTime >= 0.5 then
local scaleDown = Animation.scale(
0.5,
{ width = el.width, height = el.height },
{ width = 400, height = 200 }
)
scaleDown.easing = function(t) return t < 0.5 and 2 * t * t or -1 + (4 - 2 * t) * t end
el.animation = scaleDown
el._scaleCallback = nil
end
end
end
end,
})
-- ========================================
-- Section 3: Easing Functions Comparison
-- ========================================
Gui.new({
x = "2vw",
y = "36vh",
width = "96vw",
height = "3vh",
text = "Easing Functions - Click any box to see different easing",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local easingContainer = Gui.new({
x = "2vw",
y = "40vh",
width = "96vw",
height = "56vh",
positioning = enums.Positioning.GRID,
gridRows = 3,
gridColumns = 3,
rowGap = "2vh",
columnGap = "2vw",
backgroundColor = Color.new(0.08, 0.08, 0.12, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.25, 0.25, 0.35, 1),
padding = { top = 20, right = 20, bottom = 20, left = 20 },
})
-- Different easing functions
local easings = {
{ name = "Linear", func = function(t) return t end },
{ name = "EaseInQuad", func = function(t) return t * t end },
{ name = "EaseOutQuad", func = function(t) return t * (2 - t) end },
{ name = "EaseInOutQuad", func = function(t) return t < 0.5 and 2 * t * t or -1 + (4 - 2 * t) * t end },
{ name = "EaseInCubic", func = function(t) return t * t * t end },
{ name = "EaseOutCubic", func = function(t) local t1 = t - 1; return t1 * t1 * t1 + 1 end },
{ name = "EaseInQuart", func = function(t) return t * t * t * t end },
{ name = "EaseOutQuart", func = function(t) local t1 = t - 1; return 1 - t1 * t1 * t1 * t1 end },
{ name = "EaseInExpo", func = function(t) return t == 0 and 0 or math.pow(2, 10 * (t - 1)) end },
}
easingBoxes = {}
for i, easing in ipairs(easings) do
local hue = (i - 1) / 8
local box = Gui.new({
parent = easingContainer,
backgroundColor = Color.new(0.2 + hue * 0.6, 0.4 + math.sin(hue * 3.14) * 0.4, 0.8 - hue * 0.4, 1),
text = easing.name,
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 8,
callback = function(element, event)
if event.type == "click" then
-- Fade out and in with this easing
local fadeOut = Animation.fade(0.8, 1.0, 0.2)
fadeOut.easing = easing.func
element.animation = fadeOut
local startTime = Lv.timer.getTime()
element._easingCallback = function(el, dt)
if Lv.timer.getTime() - startTime >= 0.8 then
local fadeIn = Animation.fade(0.8, 0.2, 1.0)
fadeIn.easing = easing.func
el.animation = fadeIn
el._easingCallback = nil
end
end
end
end,
})
table.insert(easingBoxes, box)
end
end
function Lv.update(dt)
-- Handle fade callback
if fadeBox and fadeBox._fadeCallback then
fadeBox._fadeCallback(fadeBox, dt)
end
-- Handle scale callback
if scaleBox and scaleBox._scaleCallback then
scaleBox._scaleCallback(scaleBox, dt)
end
-- Handle easing callbacks
for _, box in ipairs(easingBoxes) do
if box._easingCallback then
box._easingCallback(box, dt)
end
end
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -0,0 +1,319 @@
--[[
FlexLove Example 06: Event System
This example demonstrates the event system in FlexLove:
- Click events (left, right, middle mouse buttons)
- Press and release events
- Event properties (position, modifiers, click count)
- Double-click detection
- Keyboard modifiers (Shift, Ctrl, Alt)
Run with: love /path/to/libs/examples/06_event_system.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
-- Event log
local eventLog = {}
local maxLogEntries = 15
local function addLogEntry(text)
table.insert(eventLog, 1, text)
if #eventLog > maxLogEntries then
table.remove(eventLog)
end
end
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "6vh",
text = "FlexLove Example 06: Event System",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 1: Click Events
-- ========================================
Gui.new({
x = "2vw",
y = "10vh",
width = "46vw",
height = "3vh",
text = "Click Events - Try left, right, middle mouse buttons",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local clickBox = Gui.new({
x = "2vw",
y = "14vh",
width = "46vw",
height = "20vh",
backgroundColor = Color.new(0.3, 0.5, 0.7, 1),
text = "Click me with different mouse buttons!",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
callback = function(element, event)
local buttonName = event.button == 1 and "Left" or (event.button == 2 and "Right" or "Middle")
local eventTypeName = event.type:sub(1,1):upper() .. event.type:sub(2)
if event.type == "click" or event.type == "rightclick" or event.type == "middleclick" then
addLogEntry(string.format("%s Click at (%.0f, %.0f) - Count: %d",
buttonName, event.x, event.y, event.clickCount))
elseif event.type == "press" then
addLogEntry(string.format("%s Button Pressed at (%.0f, %.0f)",
buttonName, event.x, event.y))
elseif event.type == "release" then
addLogEntry(string.format("%s Button Released at (%.0f, %.0f)",
buttonName, event.x, event.y))
end
end,
})
-- ========================================
-- Section 2: Keyboard Modifiers
-- ========================================
Gui.new({
x = "50vw",
y = "10vh",
width = "48vw",
height = "3vh",
text = "Keyboard Modifiers - Hold Shift/Ctrl/Alt while clicking",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local modifierBox = Gui.new({
x = "50vw",
y = "14vh",
width = "48vw",
height = "20vh",
backgroundColor = Color.new(0.7, 0.4, 0.5, 1),
text = "Click with modifiers!",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
callback = function(element, event)
if event.type == "click" then
local mods = {}
if event.modifiers.shift then table.insert(mods, "Shift") end
if event.modifiers.ctrl then table.insert(mods, "Ctrl") end
if event.modifiers.alt then table.insert(mods, "Alt") end
if event.modifiers.super then table.insert(mods, "Super") end
local modText = #mods > 0 and table.concat(mods, "+") or "None"
addLogEntry(string.format("Click with modifiers: %s", modText))
end
end,
})
-- ========================================
-- Section 3: Double-Click Detection
-- ========================================
Gui.new({
x = "2vw",
y = "36vh",
width = "46vw",
height = "3vh",
text = "Double-Click Detection",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local doubleClickBox = Gui.new({
x = "2vw",
y = "40vh",
width = "46vw",
height = "15vh",
backgroundColor = Color.new(0.5, 0.7, 0.4, 1),
text = "Double-click me!",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
callback = function(element, event)
if event.type == "click" then
if event.clickCount == 1 then
addLogEntry("Single click detected")
elseif event.clickCount == 2 then
addLogEntry("DOUBLE CLICK detected!")
-- Visual feedback for double-click
element.backgroundColor = Color.new(0.9, 0.9, 0.3, 1)
elseif event.clickCount >= 3 then
addLogEntry(string.format("TRIPLE+ CLICK detected! (count: %d)", event.clickCount))
element.backgroundColor = Color.new(0.9, 0.3, 0.9, 1)
end
-- Reset color after a delay (simulated in update)
element._resetTime = Lv.timer.getTime() + 0.3
end
end,
})
-- ========================================
-- Section 4: Event Log Display
-- ========================================
Gui.new({
x = "50vw",
y = "36vh",
width = "48vw",
height = "3vh",
text = "Event Log (most recent first)",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Event log container
local logContainer = Gui.new({
x = "50vw",
y = "40vh",
width = "48vw",
height = "56vh",
backgroundColor = Color.new(0.08, 0.08, 0.12, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.25, 0.25, 0.35, 1),
cornerRadius = 5,
})
-- ========================================
-- Section 5: Interactive Buttons
-- ========================================
Gui.new({
x = "2vw",
y = "57vh",
width = "46vw",
height = "3vh",
text = "Interactive Buttons",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local buttonContainer = Gui.new({
x = "2vw",
y = "61vh",
width = "46vw",
height = "35vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.VERTICAL,
justifyContent = enums.JustifyContent.SPACE_EVENLY,
alignItems = enums.AlignItems.STRETCH,
backgroundColor = Color.new(0.1, 0.1, 0.15, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.3, 0.4, 1),
padding = { top = 10, right = 10, bottom = 10, left = 10 },
gap = 10,
})
-- Button 1: Press/Release events
Gui.new({
parent = buttonContainer,
height = "8vh",
backgroundColor = Color.new(0.4, 0.5, 0.8, 1),
text = "Press and Release Events",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
callback = function(element, event)
if event.type == "press" then
addLogEntry("Button 1: PRESSED")
element.backgroundColor = Color.new(0.2, 0.3, 0.6, 1)
elseif event.type == "release" then
addLogEntry("Button 1: RELEASED")
element.backgroundColor = Color.new(0.4, 0.5, 0.8, 1)
end
end,
})
-- Button 2: Click counter
local clickCounter = 0
Gui.new({
parent = buttonContainer,
height = "8vh",
backgroundColor = Color.new(0.8, 0.5, 0.4, 1),
text = "Click Counter: 0",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
callback = function(element, event)
if event.type == "click" then
clickCounter = clickCounter + 1
element.text = "Click Counter: " .. clickCounter
addLogEntry("Button 2: Click #" .. clickCounter)
end
end,
})
-- Button 3: Clear log
Gui.new({
parent = buttonContainer,
height = "8vh",
backgroundColor = Color.new(0.6, 0.4, 0.6, 1),
text = "Clear Event Log",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
callback = function(element, event)
if event.type == "click" then
eventLog = {}
addLogEntry("Log cleared!")
end
end,
})
end
function Lv.update(dt)
-- Reset double-click box color
if doubleClickBox and doubleClickBox._resetTime and Lv.timer.getTime() >= doubleClickBox._resetTime then
doubleClickBox.backgroundColor = Color.new(0.5, 0.7, 0.4, 1)
doubleClickBox._resetTime = nil
end
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
-- Draw event log
Lv.graphics.setColor(0.8, 0.9, 1, 1)
local logX = Lv.graphics.getWidth() * 0.50 + 10
local logY = Lv.graphics.getHeight() * 0.40 + 10
local lineHeight = 20
for i, entry in ipairs(eventLog) do
local alpha = 1.0 - (i - 1) / maxLogEntries * 0.5
Lv.graphics.setColor(0.8, 0.9, 1, alpha)
Lv.graphics.print(entry, logX, logY + (i - 1) * lineHeight)
end
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -0,0 +1,189 @@
--[[
FlexLove Example 07: Text Rendering
This example demonstrates text rendering features in FlexLove:
- Text alignment (start, center, end, justify)
- Text size presets (xxs, xs, sm, md, lg, xl, xxl, 3xl, 4xl)
- Font rendering with custom fonts
- Text wrapping and positioning
Run with: love /path/to/libs/examples/07_text_rendering.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "6vh",
text = "FlexLove Example 07: Text Rendering",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 1: Text Alignment
-- ========================================
Gui.new({
x = "2vw",
y = "10vh",
width = "96vw",
height = "3vh",
text = "Text Alignment Options",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local alignments = {
{ name = "START", value = enums.TextAlign.START },
{ name = "CENTER", value = enums.TextAlign.CENTER },
{ name = "END", value = enums.TextAlign.END },
}
local yOffset = 14
for _, align in ipairs(alignments) do
Gui.new({
x = "2vw",
y = yOffset .. "vh",
width = "30vw",
height = "8vh",
backgroundColor = Color.new(0.15, 0.15, 0.2, 1),
text = "Align: " .. align.name,
textSize = "2vh",
textColor = Color.new(0.8, 0.9, 1, 1),
textAlign = align.value,
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.3, 0.4, 1),
cornerRadius = 5,
})
yOffset = yOffset + 9
end
-- ========================================
-- Section 2: Text Size Presets
-- ========================================
Gui.new({
x = "34vw",
y = "10vh",
width = "64vw",
height = "3vh",
text = "Text Size Presets",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local textSizes = {
{ name = "XXS", value = "xxs" },
{ name = "XS", value = "xs" },
{ name = "SM", value = "sm" },
{ name = "MD", value = "md" },
{ name = "LG", value = "lg" },
{ name = "XL", value = "xl" },
{ name = "XXL", value = "xxl" },
{ name = "3XL", value = "3xl" },
{ name = "4XL", value = "4xl" },
}
local sizeContainer = Gui.new({
x = "34vw",
y = "14vh",
width = "64vw",
height = "76vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.VERTICAL,
justifyContent = enums.JustifyContent.FLEX_START,
alignItems = enums.AlignItems.STRETCH,
gap = 5,
backgroundColor = Color.new(0.08, 0.08, 0.12, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.25, 0.25, 0.35, 1),
padding = { top = 10, right = 10, bottom = 10, left = 10 },
})
for i, size in ipairs(textSizes) do
local hue = (i - 1) / 8
Gui.new({
parent = sizeContainer,
height = "7vh",
backgroundColor = Color.new(0.2 + hue * 0.3, 0.3 + hue * 0.2, 0.5 - hue * 0.2, 1),
text = size.name .. " - The quick brown fox jumps over the lazy dog",
textSize = size.value,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.START,
cornerRadius = 3,
})
end
-- ========================================
-- Section 3: Custom Font Sizes (vh units)
-- ========================================
Gui.new({
x = "2vw",
y = "41vh",
width = "30vw",
height = "3vh",
text = "Custom Text Sizes (vh units)",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local customSizes = { "1vh", "2vh", "3vh", "4vh", "5vh" }
local customContainer = Gui.new({
x = "2vw",
y = "45vh",
width = "30vw",
height = "45vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.VERTICAL,
justifyContent = enums.JustifyContent.SPACE_EVENLY,
alignItems = enums.AlignItems.STRETCH,
backgroundColor = Color.new(0.1, 0.12, 0.15, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.35, 0.4, 1),
padding = { top = 10, right = 10, bottom = 10, left = 10 },
})
for i, size in ipairs(customSizes) do
Gui.new({
parent = customContainer,
backgroundColor = Color.new(0.3, 0.4 + i * 0.08, 0.6 - i * 0.08, 1),
text = size .. " text",
textSize = size,
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 3,
padding = { top = 5, right = 10, bottom = 5, left = 10 },
})
end
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -0,0 +1,236 @@
--[[
FlexLove Example 08: Absolute vs Relative Positioning
This example demonstrates positioning modes in FlexLove:
- Absolute positioning (fixed coordinates)
- Relative positioning (relative to siblings)
- Comparison between the two modes
- Practical use cases
Run with: love /path/to/libs/examples/08_absolute_relative_positioning.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "6vh",
text = "FlexLove Example 08: Absolute vs Relative Positioning",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 1: Absolute Positioning
-- ========================================
Gui.new({
x = "2vw",
y = "10vh",
width = "46vw",
height = "3vh",
text = "Absolute Positioning - Fixed coordinates",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local absoluteContainer = Gui.new({
x = "2vw",
y = "14vh",
width = "46vw",
height = "40vh",
positioning = enums.Positioning.ABSOLUTE,
backgroundColor = Color.new(0.1, 0.1, 0.15, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.3, 0.4, 1),
})
-- Absolute positioned children
Gui.new({
parent = absoluteContainer,
x = 20,
y = 20,
width = 150,
height = 80,
positioning = enums.Positioning.ABSOLUTE,
backgroundColor = Color.new(0.8, 0.3, 0.3, 1),
text = "x:20, y:20",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
Gui.new({
parent = absoluteContainer,
x = 200,
y = 50,
width = 150,
height = 80,
positioning = enums.Positioning.ABSOLUTE,
backgroundColor = Color.new(0.3, 0.8, 0.3, 1),
text = "x:200, y:50",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
Gui.new({
parent = absoluteContainer,
x = 100,
y = 150,
width = 150,
height = 80,
positioning = enums.Positioning.ABSOLUTE,
backgroundColor = Color.new(0.3, 0.3, 0.8, 1),
text = "x:100, y:150",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
Gui.new({
parent = absoluteContainer,
x = 280,
y = 180,
width = 150,
height = 80,
positioning = enums.Positioning.ABSOLUTE,
backgroundColor = Color.new(0.8, 0.8, 0.3, 1),
text = "x:280, y:180",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
-- ========================================
-- Section 2: Relative Positioning
-- ========================================
Gui.new({
x = "50vw",
y = "10vh",
width = "48vw",
height = "3vh",
text = "Relative Positioning - Flows with siblings",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local relativeContainer = Gui.new({
x = "50vw",
y = "14vh",
width = "48vw",
height = "40vh",
positioning = enums.Positioning.RELATIVE,
backgroundColor = Color.new(0.1, 0.15, 0.1, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.3, 0.4, 0.3, 1),
padding = { top = 10, right = 10, bottom = 10, left = 10 },
})
-- Relative positioned children (flow naturally)
local relativeColors = {
Color.new(0.8, 0.3, 0.3, 1),
Color.new(0.3, 0.8, 0.3, 1),
Color.new(0.3, 0.3, 0.8, 1),
Color.new(0.8, 0.8, 0.3, 1),
}
for i = 1, 4 do
Gui.new({
parent = relativeContainer,
width = "45%",
height = "8vh",
positioning = enums.Positioning.RELATIVE,
backgroundColor = relativeColors[i],
text = "Element " .. i .. " (relative)",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
margin = { top = 5, right = 5, bottom = 5, left = 5 },
})
end
-- ========================================
-- Section 3: Comparison with Overlapping
-- ========================================
Gui.new({
x = "2vw",
y = "56vh",
width = "96vw",
height = "3vh",
text = "Absolute Positioning Allows Overlapping",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local overlapContainer = Gui.new({
x = "2vw",
y = "60vh",
width = "96vw",
height = "36vh",
positioning = enums.Positioning.ABSOLUTE,
backgroundColor = Color.new(0.08, 0.08, 0.12, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.25, 0.25, 0.35, 1),
})
-- Create overlapping elements
local overlapColors = {
Color.new(0.9, 0.3, 0.3, 0.7),
Color.new(0.3, 0.9, 0.3, 0.7),
Color.new(0.3, 0.3, 0.9, 0.7),
Color.new(0.9, 0.9, 0.3, 0.7),
}
for i = 1, 4 do
Gui.new({
parent = overlapContainer,
x = 50 + (i - 1) * 60,
y = 30 + (i - 1) * 40,
width = 300,
height = 150,
positioning = enums.Positioning.ABSOLUTE,
backgroundColor = overlapColors[i],
text = "Layer " .. i,
textSize = "2.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
z = i, -- Z-index for layering
})
end
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -0,0 +1,281 @@
--[[
FlexLove Example 09: Styling and Visual Effects
This example demonstrates styling and visual effects:
- Corner radius (uniform and individual corners)
- Borders (different sides)
- Opacity levels
- Background colors
- Blur effects (contentBlur and backdropBlur)
Run with: love /path/to/libs/examples/09_styling_visual_effects.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "5vh",
text = "FlexLove Example 09: Styling and Visual Effects",
textSize = "3.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 1: Corner Radius
-- ========================================
Gui.new({
x = "2vw",
y = "9vh",
width = "46vw",
height = "3vh",
text = "Corner Radius",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Uniform corner radius
Gui.new({
x = "2vw",
y = "13vh",
width = "14vw",
height = "12vh",
backgroundColor = Color.new(0.6, 0.3, 0.7, 1),
cornerRadius = 5,
text = "radius: 5",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
Gui.new({
x = "17vw",
y = "13vh",
width = "14vw",
height = "12vh",
backgroundColor = Color.new(0.3, 0.6, 0.7, 1),
cornerRadius = 15,
text = "radius: 15",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Individual corner radius
Gui.new({
x = "32vw",
y = "13vh",
width = "16vw",
height = "12vh",
backgroundColor = Color.new(0.7, 0.6, 0.3, 1),
cornerRadius = {
topLeft = 0,
topRight = 20,
bottomLeft = 20,
bottomRight = 0,
},
text = "Individual\ncorners",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 2: Borders
-- ========================================
Gui.new({
x = "50vw",
y = "9vh",
width = "48vw",
height = "3vh",
text = "Borders",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- All borders
Gui.new({
x = "50vw",
y = "13vh",
width = "14vw",
height = "12vh",
backgroundColor = Color.new(0.2, 0.2, 0.3, 1),
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.8, 0.4, 0.4, 1),
text = "All sides",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Top and bottom borders
Gui.new({
x = "65vw",
y = "13vh",
width = "14vw",
height = "12vh",
backgroundColor = Color.new(0.2, 0.3, 0.2, 1),
border = { top = true, bottom = true },
borderColor = Color.new(0.4, 0.8, 0.4, 1),
text = "Top & Bottom",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Left border only
Gui.new({
x = "80vw",
y = "13vh",
width = "16vw",
height = "12vh",
backgroundColor = Color.new(0.2, 0.2, 0.3, 1),
border = { left = true },
borderColor = Color.new(0.4, 0.4, 0.8, 1),
text = "Left only",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- ========================================
-- Section 3: Opacity
-- ========================================
Gui.new({
x = "2vw",
y = "27vh",
width = "96vw",
height = "3vh",
text = "Opacity Levels",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local opacityLevels = { 1.0, 0.75, 0.5, 0.25 }
for i, opacity in ipairs(opacityLevels) do
Gui.new({
x = (2 + (i - 1) * 24) .. "vw",
y = "31vh",
width = "22vw",
height = "12vh",
backgroundColor = Color.new(0.8, 0.3, 0.5, 1),
opacity = opacity,
text = "Opacity: " .. opacity,
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 8,
})
end
-- ========================================
-- Section 4: Background Colors
-- ========================================
Gui.new({
x = "2vw",
y = "45vh",
width = "96vw",
height = "3vh",
text = "Background Colors",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Gradient-like colors
for i = 1, 8 do
local hue = (i - 1) / 7
Gui.new({
x = (2 + (i - 1) * 12) .. "vw",
y = "49vh",
width = "11vw",
height = "18vh",
backgroundColor = Color.new(
0.3 + hue * 0.5,
0.5 + math.sin(hue * 3.14) * 0.3,
0.8 - hue * 0.5,
1
),
text = tostring(i),
textSize = "3vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
})
end
-- ========================================
-- Section 5: Blur Effects (if supported)
-- ========================================
Gui.new({
x = "2vw",
y = "69vh",
width = "96vw",
height = "3vh",
text = "Blur Effects (contentBlur & backdropBlur)",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Content blur example
Gui.new({
x = "2vw",
y = "73vh",
width = "46vw",
height = "22vh",
backgroundColor = Color.new(0.3, 0.4, 0.6, 0.8),
contentBlur = { intensity = 5, quality = 3 },
text = "Content Blur\n(blurs this element)",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
})
-- Backdrop blur example
Gui.new({
x = "50vw",
y = "73vh",
width = "46vw",
height = "22vh",
backgroundColor = Color.new(0.6, 0.4, 0.3, 0.6),
backdropBlur = { intensity = 10, quality = 5 },
text = "Backdrop Blur\n(blurs background)",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
})
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -0,0 +1,206 @@
--[[
FlexLove Example 10: Padding and Margins
This example demonstrates padding and margin spacing:
- Uniform padding
- Individual padding sides
- Uniform margins
- Individual margin sides
- Visual indicators for spacing
Run with: love /path/to/libs/examples/10_padding_margins.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "5vh",
text = "FlexLove Example 10: Padding and Margins",
textSize = "3.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Section 1: Padding Examples
Gui.new({
x = "2vw",
y = "9vh",
width = "46vw",
height = "3vh",
text = "Padding (internal spacing)",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Uniform padding
local container1 = Gui.new({
x = "2vw",
y = "13vh",
width = "22vw",
height = "18vh",
backgroundColor = Color.new(0.3, 0.4, 0.6, 1),
padding = { top = 20, right = 20, bottom = 20, left = 20 },
})
Gui.new({
parent = container1,
width = "auto",
height = "auto",
backgroundColor = Color.new(0.8, 0.6, 0.3, 1),
text = "Uniform\npadding: 20px",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Individual padding sides
local container2 = Gui.new({
x = "26vw",
y = "13vh",
width = "22vw",
height = "18vh",
backgroundColor = Color.new(0.4, 0.6, 0.3, 1),
padding = { top = 30, right = 10, bottom = 30, left = 10 },
})
Gui.new({
parent = container2,
width = "auto",
height = "auto",
backgroundColor = Color.new(0.8, 0.3, 0.6, 1),
text = "Individual\ntop/bottom: 30px\nleft/right: 10px",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Section 2: Margin Examples
Gui.new({
x = "50vw",
y = "9vh",
width = "48vw",
height = "3vh",
text = "Margins (external spacing)",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Container to show margins
local marginContainer = Gui.new({
x = "50vw",
y = "13vh",
width = "46vw",
height = "38vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.VERTICAL,
backgroundColor = Color.new(0.15, 0.15, 0.2, 1),
})
-- Elements with different margins
Gui.new({
parent = marginContainer,
width = "40vw",
height = "6vh",
backgroundColor = Color.new(0.7, 0.3, 0.3, 1),
margin = { top = 10, right = 10, bottom = 10, left = 10 },
text = "Uniform margin: 10px",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
Gui.new({
parent = marginContainer,
width = "40vw",
height = "6vh",
backgroundColor = Color.new(0.3, 0.7, 0.3, 1),
margin = { top = 5, right = 30, bottom = 5, left = 30 },
text = "Horizontal margin: 30px",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
Gui.new({
parent = marginContainer,
width = "40vw",
height = "6vh",
backgroundColor = Color.new(0.3, 0.3, 0.7, 1),
margin = { top = 20, right = 10, bottom = 20, left = 10 },
text = "Vertical margin: 20px",
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Section 3: Combined Padding and Margins
Gui.new({
x = "2vw",
y = "33vh",
width = "46vw",
height = "3vh",
text = "Combined Padding & Margins",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local combinedContainer = Gui.new({
x = "2vw",
y = "37vh",
width = "46vw",
height = "58vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.VERTICAL,
backgroundColor = Color.new(0.1, 0.1, 0.15, 1),
padding = { top = 15, right = 15, bottom = 15, left = 15 },
})
for i = 1, 3 do
local box = Gui.new({
parent = combinedContainer,
width = "auto",
height = "14vh",
backgroundColor = Color.new(0.5 + i * 0.1, 0.4, 0.6 - i * 0.1, 1),
margin = { top = 10, right = 0, bottom = 10, left = 0 },
padding = { top = 15, right = 15, bottom = 15, left = 15 },
})
Gui.new({
parent = box,
width = "auto",
height = "auto",
backgroundColor = Color.new(0.2, 0.2, 0.3, 1),
text = "Box " .. i .. "\nPadding: 15px\nMargin: 10px",
textSize = "1.8vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
end
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -0,0 +1,149 @@
--[[
FlexLove Example 11: Input Controls
This example demonstrates input controls (if available):
- Text input fields
- Keyboard input handling
- Focus management
Run with: love /path/to/libs/examples/11_input_controls.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "6vh",
text = "FlexLove Example 11: Input Controls",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Note: Input controls may require additional setup in FlexLove
Gui.new({
x = "2vw",
y = "10vh",
width = "96vw",
height = "4vh",
text = "Note: This example demonstrates basic input handling patterns",
textSize = "2vh",
textColor = Color.new(0.8, 0.8, 0.8, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Interactive buttons
local counter = 0
local counterDisplay = Gui.new({
x = "35vw",
y = "20vh",
width = "30vw",
height = "10vh",
backgroundColor = Color.new(0.2, 0.2, 0.3, 1),
text = "Counter: 0",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
})
-- Increment button
Gui.new({
x = "20vw",
y = "35vh",
width = "20vw",
height = "8vh",
backgroundColor = Color.new(0.3, 0.6, 0.3, 1),
text = "Increment (+)",
textSize = "2.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 8,
callback = function()
counter = counter + 1
counterDisplay.text = "Counter: " .. counter
end
})
-- Decrement button
Gui.new({
x = "60vw",
y = "35vh",
width = "20vw",
height = "8vh",
backgroundColor = Color.new(0.6, 0.3, 0.3, 1),
text = "Decrement (-)",
textSize = "2.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 8,
callback = function()
counter = counter - 1
counterDisplay.text = "Counter: " .. counter
end
})
-- Reset button
Gui.new({
x = "40vw",
y = "46vh",
width = "20vw",
height = "8vh",
backgroundColor = Color.new(0.4, 0.4, 0.6, 1),
text = "Reset",
textSize = "2.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 8,
callback = function()
counter = 0
counterDisplay.text = "Counter: " .. counter
end
})
-- Keyboard input info
Gui.new({
x = "2vw",
y = "60vh",
width = "96vw",
height = "30vh",
backgroundColor = Color.new(0.15, 0.15, 0.2, 1),
text = "Keyboard Input:\n\nFlexLove supports keyboard input through Element properties:\n\n" ..
"- Use 'inputType' property for text input fields\n" ..
"- Handle onTextInput, onTextChange, onEnter callbacks\n" ..
"- Manage focus with onFocus and onBlur events\n\n" ..
"See FlexLove documentation for full input API",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(0.4, 0.4, 0.5, 1),
})
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -0,0 +1,204 @@
--[[
FlexLove Example 12: Z-Index Layering
This example demonstrates z-index for element layering:
- Different z-index values
- Overlapping elements
- Layer ordering
Run with: love /path/to/libs/examples/12_z_index_layering.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local enums = FlexLove.enums
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 }
})
-- Title
Gui.new({
x = "2vw",
y = "2vh",
width = "96vw",
height = "6vh",
text = "FlexLove Example 12: Z-Index Layering",
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Description
Gui.new({
x = "2vw",
y = "10vh",
width = "96vw",
height = "3vh",
text = "Elements with higher z-index values appear on top",
textSize = "2vh",
textColor = Color.new(0.8, 0.8, 0.8, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Section 1: Overlapping boxes with different z-index
Gui.new({
x = "2vw",
y = "15vh",
width = "46vw",
height = "3vh",
text = "Overlapping Elements",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Box 1 (z-index: 1)
Gui.new({
x = "5vw",
y = "20vh",
width = "20vw",
height = "20vh",
z = 1,
backgroundColor = Color.new(0.8, 0.3, 0.3, 1),
text = "Z-Index: 1",
textSize = "2.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
})
-- Box 2 (z-index: 2) - overlaps Box 1
Gui.new({
x = "12vw",
y = "25vh",
width = "20vw",
height = "20vh",
z = 2,
backgroundColor = Color.new(0.3, 0.8, 0.3, 1),
text = "Z-Index: 2",
textSize = "2.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
})
-- Box 3 (z-index: 3) - overlaps Box 1 and 2
Gui.new({
x = "19vw",
y = "30vh",
width = "20vw",
height = "20vh",
z = 3,
backgroundColor = Color.new(0.3, 0.3, 0.8, 1),
text = "Z-Index: 3",
textSize = "2.5vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
})
-- Section 2: Cards with different layers
Gui.new({
x = "50vw",
y = "15vh",
width = "48vw",
height = "3vh",
text = "Layered Cards",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
-- Create a stack of cards
for i = 1, 5 do
Gui.new({
x = (52 + i * 2) .. "vw",
y = (18 + i * 3) .. "vh",
width = "22vw",
height = "15vh",
z = i,
backgroundColor = Color.new(0.3 + i * 0.1, 0.4, 0.7 - i * 0.1, 1),
text = "Card " .. i .. "\nZ-Index: " .. i,
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 8,
border = { top = true, right = true, bottom = true, left = true },
borderColor = Color.new(1, 1, 1, 0.3),
})
end
-- Section 3: Interactive z-index demo
Gui.new({
x = "2vw",
y = "53vh",
width = "96vw",
height = "3vh",
text = "Click to Bring to Front",
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
local maxZ = 10
-- Create interactive boxes
for i = 1, 4 do
local box = Gui.new({
x = (5 + (i - 1) * 22) .. "vw",
y = "58vh",
width = "20vw",
height = "20vh",
z = i,
backgroundColor = Color.new(
0.4 + i * 0.1,
0.5 + math.sin(i) * 0.2,
0.7 - i * 0.1,
1
),
text = "Box " .. i .. "\nClick me!",
textSize = "2.2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 10,
})
-- Add click handler to bring to front
box.callback = function(element)
maxZ = maxZ + 1
element.z = maxZ
element.text = "Box " .. i .. "\nZ: " .. element.z
end
end
-- Info text
Gui.new({
x = "2vw",
y = "82vh",
width = "96vw",
height = "14vh",
backgroundColor = Color.new(0.15, 0.15, 0.2, 1),
text = "Z-Index determines the stacking order of elements.\n" ..
"Higher values appear on top of lower values.\n" ..
"Click the boxes above to bring them to the front!",
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 8,
})
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -0,0 +1,274 @@
--[[
FlexLove Example 13: Comprehensive Demo
This example combines multiple FlexLove features into a polished demo:
- Flex and grid layouts
- Themed components
- Animations
- Event handling
- Responsive design
Run with: love /path/to/libs/examples/13_comprehensive_demo.lua
]]
local Lv = love
local FlexLove = require("../FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
local Animation = FlexLove.Animation
local enums = FlexLove.enums
function Lv.load()
Gui.init({
baseScale = { width = 1920, height = 1080 },
theme = "space"
})
-- Header
local header = Gui.new({
x = 0,
y = 0,
width = "100vw",
height = "12vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.HORIZONTAL,
justifyContent = enums.JustifyContent.SPACE_BETWEEN,
alignItems = enums.AlignItems.CENTER,
backgroundColor = Color.new(0.1, 0.1, 0.15, 1),
padding = { top = 10, right = 20, bottom = 10, left = 20 },
border = { bottom = true },
borderColor = Color.new(0.3, 0.3, 0.4, 1),
})
-- Logo/Title
Gui.new({
parent = header,
width = "auto",
height = "auto",
text = "FlexLove Demo",
textSize = "4vh",
textColor = Color.new(0.8, 0.9, 1, 1),
})
-- Header buttons
local headerButtons = Gui.new({
parent = header,
width = "auto",
height = "auto",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.HORIZONTAL,
gap = 10,
})
local buttonNames = { "Home", "Features", "About" }
for _, name in ipairs(buttonNames) do
Gui.new({
parent = headerButtons,
width = "8vw",
height = "6vh",
backgroundColor = Color.new(0.3, 0.4, 0.6, 1),
text = name,
textSize = "2vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
cornerRadius = 5,
themeComponent = "button",
})
end
-- Main content area
local mainContent = Gui.new({
x = 0,
y = "12vh",
width = "100vw",
height = "88vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.HORIZONTAL,
backgroundColor = Color.new(0.05, 0.05, 0.08, 1),
})
-- Sidebar
local sidebar = Gui.new({
parent = mainContent,
width = "20vw",
height = "88vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.VERTICAL,
backgroundColor = Color.new(0.08, 0.08, 0.12, 1),
padding = { top = 20, right = 10, bottom = 20, left = 10 },
gap = 10,
border = { right = true },
borderColor = Color.new(0.2, 0.2, 0.3, 1),
})
-- Sidebar menu items
local menuItems = {
{ icon = "", label = "Dashboard" },
{ icon = "", label = "Analytics" },
{ icon = "", label = "Settings" },
{ icon = "", label = "Profile" },
{ icon = "", label = "Help" },
}
for _, item in ipairs(menuItems) do
local menuButton = Gui.new({
parent = sidebar,
width = "auto",
height = "7vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.HORIZONTAL,
alignItems = enums.AlignItems.CENTER,
backgroundColor = Color.new(0.15, 0.15, 0.2, 1),
padding = { top = 10, right = 15, bottom = 10, left = 15 },
cornerRadius = 5,
})
Gui.new({
parent = menuButton,
width = "auto",
height = "auto",
text = item.icon .. " " .. item.label,
textSize = "2vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
})
end
-- Content panel
local contentPanel = Gui.new({
parent = mainContent,
width = "80vw",
height = "88vh",
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.VERTICAL,
padding = { top = 20, right = 20, bottom = 20, left = 20 },
gap = 15,
})
-- Welcome section
Gui.new({
parent = contentPanel,
width = "auto",
height = "auto",
text = "Welcome to FlexLove!",
textSize = "3.5vh",
textColor = Color.new(1, 1, 1, 1),
})
-- Stats grid
local statsGrid = Gui.new({
parent = contentPanel,
width = "auto",
height = "20vh",
positioning = enums.Positioning.GRID,
gridRows = 1,
gridColumns = 4,
columnGap = 15,
})
local stats = {
{ label = "Projects", value = "24", color = Color.new(0.3, 0.6, 0.8, 1) },
{ label = "Users", value = "1.2K", color = Color.new(0.6, 0.3, 0.8, 1) },
{ label = "Revenue", value = "$45K", color = Color.new(0.8, 0.6, 0.3, 1) },
{ label = "Growth", value = "+12%", color = Color.new(0.3, 0.8, 0.6, 1) },
}
for _, stat in ipairs(stats) do
local statCard = Gui.new({
parent = statsGrid,
positioning = enums.Positioning.FLEX,
flexDirection = enums.FlexDirection.VERTICAL,
justifyContent = enums.JustifyContent.CENTER,
alignItems = enums.AlignItems.CENTER,
backgroundColor = stat.color,
cornerRadius = 8,
padding = { top = 15, right = 15, bottom = 15, left = 15 },
})
Gui.new({
parent = statCard,
width = "auto",
height = "auto",
text = stat.value,
textSize = "4vh",
textColor = Color.new(1, 1, 1, 1),
textAlign = enums.TextAlign.CENTER,
})
Gui.new({
parent = statCard,
width = "auto",
height = "auto",
text = stat.label,
textSize = "1.8vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
textAlign = enums.TextAlign.CENTER,
})
end
-- Feature cards
local cardsContainer = Gui.new({
parent = contentPanel,
width = "auto",
height = "auto",
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 3,
rowGap = 15,
columnGap = 15,
})
local features = {
"Flexbox Layout",
"Grid System",
"Theming",
"Animations",
"Events",
"Responsive"
}
for i, feature in ipairs(features) do
local card = Gui.new({
parent = cardsContainer,
positioning = enums.Positioning.FLEX,
justifyContent = enums.JustifyContent.CENTER,
alignItems = enums.AlignItems.CENTER,
backgroundColor = Color.new(0.15, 0.15, 0.2, 1),
cornerRadius = 10,
padding = { top = 20, right = 20, bottom = 20, left = 20 },
})
Gui.new({
parent = card,
width = "auto",
height = "auto",
text = feature,
textSize = "2.5vh",
textColor = Color.new(0.9, 0.9, 0.9, 1),
textAlign = enums.TextAlign.CENTER,
})
-- Add hover animation
card.callback = function(element)
local anim = Animation.new({
duration = 0.2,
start = { opacity = 1 },
final = { opacity = 0.8 },
})
anim:apply(element)
end
end
end
function Lv.update(dt)
Gui.update(dt)
end
function Lv.draw()
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
Gui.draw()
end
function Lv.resize(w, h)
Gui.resize(w, h)
end

View File

@@ -235,4 +235,4 @@ function TestBlurEffects:test_quality_affects_taps()
lu.assertTrue(instance2.taps > instance1.taps, "Higher quality should result in more blur taps") lu.assertTrue(instance2.taps > instance1.taps, "Higher quality should result in more blur taps")
end end
return TestBlurEffects lu.LuaUnit.run()

View File

@@ -0,0 +1,506 @@
local lu = require("testing.luaunit")
local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
local Element = FlexLove.Element
TestKeyboardInput = {}
function TestKeyboardInput:setUp()
Gui.init({ baseScale = { width = 1920, height = 1080 } })
end
function TestKeyboardInput:tearDown()
Gui.destroy()
end
-- ====================
-- Focus Management Tests
-- ====================
function TestKeyboardInput:testFocusEditable()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
lu.assertFalse(input:isFocused())
input:focus()
lu.assertTrue(input:isFocused())
lu.assertEquals(Gui._focusedElement, input)
end
function TestKeyboardInput:testBlurEditable()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
lu.assertTrue(input:isFocused())
input:blur()
lu.assertFalse(input:isFocused())
lu.assertNil(Gui._focusedElement)
end
function TestKeyboardInput:testFocusSwitching()
local input1 = Element.new({
width = 200,
height = 40,
editable = true,
text = "Input 1",
})
local input2 = Element.new({
width = 200,
height = 40,
editable = true,
text = "Input 2",
})
input1:focus()
lu.assertTrue(input1:isFocused())
lu.assertFalse(input2:isFocused())
input2:focus()
lu.assertFalse(input1:isFocused())
lu.assertTrue(input2:isFocused())
lu.assertEquals(Gui._focusedElement, input2)
end
function TestKeyboardInput:testSelectOnFocus()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World",
selectOnFocus = true,
})
lu.assertFalse(input:hasSelection())
input:focus()
lu.assertTrue(input:hasSelection())
local startPos, endPos = input:getSelection()
lu.assertEquals(startPos, 0)
lu.assertEquals(endPos, 11) -- Length of "Hello World"
end
-- ====================
-- Text Input Tests
-- ====================
function TestKeyboardInput:testTextInput()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "",
})
input:focus()
input:textinput("H")
input:textinput("i")
lu.assertEquals(input:getText(), "Hi")
lu.assertEquals(input._cursorPosition, 2)
end
function TestKeyboardInput:testTextInputAtPosition()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(2) -- After "He"
input:textinput("X")
lu.assertEquals(input:getText(), "HeXllo")
lu.assertEquals(input._cursorPosition, 3)
end
function TestKeyboardInput:testTextInputWithSelection()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World",
})
input:focus()
input:setSelection(0, 5) -- Select "Hello"
input:textinput("Hi")
lu.assertEquals(input:getText(), "Hi World")
lu.assertEquals(input._cursorPosition, 2)
lu.assertFalse(input:hasSelection())
end
function TestKeyboardInput:testMaxLengthConstraint()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "",
maxLength = 5,
})
input:focus()
input:textinput("Hello")
lu.assertEquals(input:getText(), "Hello")
input:textinput("X") -- Should not be added
lu.assertEquals(input:getText(), "Hello")
end
-- ====================
-- Backspace/Delete Tests
-- ====================
function TestKeyboardInput:testBackspace()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(5) -- At end
input:keypressed("backspace", "backspace", false)
lu.assertEquals(input:getText(), "Hell")
lu.assertEquals(input._cursorPosition, 4)
end
function TestKeyboardInput:testBackspaceAtStart()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(0) -- At start
input:keypressed("backspace", "backspace", false)
lu.assertEquals(input:getText(), "Hello") -- No change
lu.assertEquals(input._cursorPosition, 0)
end
function TestKeyboardInput:testDelete()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(0) -- At start
input:keypressed("delete", "delete", false)
lu.assertEquals(input:getText(), "ello")
lu.assertEquals(input._cursorPosition, 0)
end
function TestKeyboardInput:testDeleteAtEnd()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(5) -- At end
input:keypressed("delete", "delete", false)
lu.assertEquals(input:getText(), "Hello") -- No change
lu.assertEquals(input._cursorPosition, 5)
end
function TestKeyboardInput:testBackspaceWithSelection()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World",
})
input:focus()
input:setSelection(0, 5) -- Select "Hello"
input:keypressed("backspace", "backspace", false)
lu.assertEquals(input:getText(), " World")
lu.assertEquals(input._cursorPosition, 0)
lu.assertFalse(input:hasSelection())
end
-- ====================
-- Cursor Movement Tests
-- ====================
function TestKeyboardInput:testArrowLeft()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(5)
input:keypressed("left", "left", false)
lu.assertEquals(input._cursorPosition, 4)
end
function TestKeyboardInput:testArrowRight()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(0)
input:keypressed("right", "right", false)
lu.assertEquals(input._cursorPosition, 1)
end
function TestKeyboardInput:testHomeKey()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(5)
input:keypressed("home", "home", false)
lu.assertEquals(input._cursorPosition, 0)
end
function TestKeyboardInput:testEndKey()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(0)
input:keypressed("end", "end", false)
lu.assertEquals(input._cursorPosition, 5)
end
function TestKeyboardInput:testEscapeClearsSelection()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:selectAll()
lu.assertTrue(input:hasSelection())
input:keypressed("escape", "escape", false)
lu.assertFalse(input:hasSelection())
lu.assertTrue(input:isFocused()) -- Still focused
end
function TestKeyboardInput:testEscapeBlurs()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
lu.assertTrue(input:isFocused())
input:keypressed("escape", "escape", false)
lu.assertFalse(input:isFocused())
end
-- ====================
-- Callback Tests
-- ====================
function TestKeyboardInput:testOnTextChangeCallback()
local changeCount = 0
local oldTextValue = nil
local newTextValue = nil
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
onTextChange = function(element, newText, oldText)
changeCount = changeCount + 1
newTextValue = newText
oldTextValue = oldText
end,
})
input:focus()
input:textinput("X")
lu.assertEquals(changeCount, 1)
lu.assertEquals(oldTextValue, "Hello")
lu.assertEquals(newTextValue, "HelloX")
end
function TestKeyboardInput:testOnTextInputCallback()
local inputCount = 0
local lastChar = nil
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "",
onTextInput = function(element, text)
inputCount = inputCount + 1
lastChar = text
end,
})
input:focus()
input:textinput("A")
input:textinput("B")
lu.assertEquals(inputCount, 2)
lu.assertEquals(lastChar, "B")
end
function TestKeyboardInput:testOnEnterCallback()
local enterCalled = false
local input = Element.new({
width = 200,
height = 40,
editable = true,
multiline = false,
text = "Hello",
onEnter = function(element)
enterCalled = true
end,
})
input:focus()
input:keypressed("return", "return", false)
lu.assertTrue(enterCalled)
end
function TestKeyboardInput:testOnFocusCallback()
local focusCalled = false
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
onFocus = function(element)
focusCalled = true
end,
})
input:focus()
lu.assertTrue(focusCalled)
end
function TestKeyboardInput:testOnBlurCallback()
local blurCalled = false
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
onBlur = function(element)
blurCalled = true
end,
})
input:focus()
input:blur()
lu.assertTrue(blurCalled)
end
-- ====================
-- GUI-level Input Forwarding Tests
-- ====================
function TestKeyboardInput:testGuiTextinputForwarding()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "",
})
input:focus()
Gui.textinput("A")
lu.assertEquals(input:getText(), "A")
end
function TestKeyboardInput:testGuiKeypressedForwarding()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(5)
Gui.keypressed("backspace", "backspace", false)
lu.assertEquals(input:getText(), "Hell")
end
function TestKeyboardInput:testGuiInputWithoutFocus()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
-- No focus
Gui.textinput("X")
lu.assertEquals(input:getText(), "Hello") -- No change
end
lu.LuaUnit.run()

View File

@@ -27,6 +27,7 @@ local testFiles = {
"testing/__tests__/21_image_scaler_nearest_tests.lua", "testing/__tests__/21_image_scaler_nearest_tests.lua",
"testing/__tests__/22_image_scaler_bilinear_tests.lua", "testing/__tests__/22_image_scaler_bilinear_tests.lua",
"testing/__tests__/23_blur_effects_tests.lua", "testing/__tests__/23_blur_effects_tests.lua",
"testing/__tests__/24_keyboard_input_tests.lua",
} }
local success = true local success = true