input, adding back examples
This commit is contained in:
775
FlexLove.lua
775
FlexLove.lua
@@ -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
|
||||||
@@ -2705,6 +2769,13 @@ function Element.new(props)
|
|||||||
self.children = {}
|
self.children = {}
|
||||||
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
|
||||||
@@ -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)
|
||||||
@@ -4677,6 +4814,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({
|
||||||
@@ -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
|
||||||
|
|||||||
219
examples/01_flex_positioning.lua
Normal file
219
examples/01_flex_positioning.lua
Normal 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
229
examples/02_grid_layout.lua
Normal 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
|
||||||
301
examples/03_theming_system.lua
Normal file
301
examples/03_theming_system.lua
Normal 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
|
||||||
241
examples/04_responsive_units.lua
Normal file
241
examples/04_responsive_units.lua
Normal 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
247
examples/05_animations.lua
Normal 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
|
||||||
319
examples/06_event_system.lua
Normal file
319
examples/06_event_system.lua
Normal 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
|
||||||
189
examples/07_text_rendering.lua
Normal file
189
examples/07_text_rendering.lua
Normal 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
|
||||||
236
examples/08_absolute_relative_positioning.lua
Normal file
236
examples/08_absolute_relative_positioning.lua
Normal 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
|
||||||
281
examples/09_styling_visual_effects.lua
Normal file
281
examples/09_styling_visual_effects.lua
Normal 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
|
||||||
206
examples/10_padding_margins.lua
Normal file
206
examples/10_padding_margins.lua
Normal 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
|
||||||
149
examples/11_input_controls.lua
Normal file
149
examples/11_input_controls.lua
Normal 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
|
||||||
204
examples/12_z_index_layering.lua
Normal file
204
examples/12_z_index_layering.lua
Normal 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
|
||||||
274
examples/13_comprehensive_demo.lua
Normal file
274
examples/13_comprehensive_demo.lua
Normal 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
|
||||||
@@ -105,7 +105,7 @@ function TestBlurEffects:test_blur_instance_quality_change()
|
|||||||
})
|
})
|
||||||
|
|
||||||
local instance1 = element:getBlurInstance()
|
local instance1 = element:getBlurInstance()
|
||||||
|
|
||||||
-- Change quality
|
-- Change quality
|
||||||
element.contentBlur.quality = 8
|
element.contentBlur.quality = 8
|
||||||
local instance2 = element:getBlurInstance()
|
local instance2 = element:getBlurInstance()
|
||||||
@@ -204,7 +204,7 @@ function TestBlurEffects:test_draw_accepts_backdrop_canvas()
|
|||||||
-- Create a mock canvas (will fail in test environment, but that's ok)
|
-- Create a mock canvas (will fail in test environment, but that's ok)
|
||||||
-- element:draw(nil)
|
-- element:draw(nil)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Test passes if we get here without syntax errors
|
-- Test passes if we get here without syntax errors
|
||||||
lu.assertTrue(true, "Draw method should accept backdrop canvas parameter")
|
lu.assertTrue(true, "Draw method should accept backdrop canvas parameter")
|
||||||
end
|
end
|
||||||
@@ -221,7 +221,7 @@ function TestBlurEffects:test_quality_affects_taps()
|
|||||||
height = 200,
|
height = 200,
|
||||||
contentBlur = { intensity = 50, quality = 1 },
|
contentBlur = { intensity = 50, quality = 1 },
|
||||||
})
|
})
|
||||||
|
|
||||||
local element2 = FlexLove.Element.new({
|
local element2 = FlexLove.Element.new({
|
||||||
width = 200,
|
width = 200,
|
||||||
height = 200,
|
height = 200,
|
||||||
@@ -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()
|
||||||
|
|||||||
506
testing/__tests__/24_keyboard_input_tests.lua
Normal file
506
testing/__tests__/24_keyboard_input_tests.lua
Normal 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()
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user