diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 9e028bc..067e99c 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -92,6 +92,7 @@ export default defineConfig({ { text: 'XML', link: '/guides/xml' }, { text: 'Canvas', link: '/guides/canvas'}, { text: 'Element Loading', link: '/guides/element-loading' }, + { text: 'Responsive Systems', link: '/guides/responsive-system' }, ] }, { diff --git a/docs/guides/reactive.md b/docs/guides/reactive.md deleted file mode 100644 index 29d04fa..0000000 --- a/docs/guides/reactive.md +++ /dev/null @@ -1,76 +0,0 @@ -# Reactive System - -The reactive system in Basalt allows you to create dynamic property values using expressions. These expressions automatically update when related values change. - -## Basic Usage - -```lua -local basalt = require("basalt") - --- Create a simple label and center it horizontally -local main = basalt.getMainFrame() - :addLabel() - :setText("Hello World") - :setX("{parent.width / 2 - self.width / 2}") -- Centers the label horizontally - --- Create a progress bar that takes 80% of parent width -main:addProgressbar() - :setWidth("{parent.width * 0.8}") - :setX("{parent.width * 0.1}") -- 10% margin on both sides -``` - -## Available Variables - -In reactive expressions, you have access to: - -- `self` - The current element -- `parent` - The parent element -- `elementName` - A given name of a element -- Any property of these elements (width, height, value, text, etc.) - -## Common Use Cases - -### Responsive Layout - -```lua -local frame = basalt.getMainFrame() - :addFrame() - :setSize("{parent.width * 0.5}", "{parent.height * 0.5}") -- Takes half of parent size - :setPosition("{parent.width / 2 - self.width / 2}", -- Centers the frame - "{parent.height / 2 - self.height / 2}") -``` - -### Dynamic Sizing - -```lua -local label = frame:addLabel() - :setText("Dynamic width based on text") - :setWidth("{#self.text}") -- Width equals text length -``` - -### Linked Properties - -```lua -local slider = frame:addSlider("mySlider") - :setPosition(2, 2) - :setSize(10, 1) - -local progress = frame:addProgressBar() - :setPosition(2, 4) - :setSize(10, 1) - :setProgress("{mySlider.value}") -- Progress syncs with slider value -``` - -## Syntax - -- Use curly braces `{}` to define a reactive expression -- Basic math operators are supported (+, -, *, /, %) -- Access properties using dot notation (element.property) -- Can use basic functions and comparisons - -## Best Practices - -1. Keep expressions simple and readable -2. Use meaningful variable names in complex calculations -3. Test expressions with different parent sizes -4. Consider edge cases (minimum/maximum sizes) diff --git a/docs/guides/responsive-system.md b/docs/guides/responsive-system.md new file mode 100644 index 0000000..d59e71b --- /dev/null +++ b/docs/guides/responsive-system.md @@ -0,0 +1,218 @@ +# Dynamic & Reactive Systems + +Basalt provides three systems for creating dynamic, responsive UIs: the **State System**, the **Responsive Plugin**, and the **Reactive Plugin**. Each serves a different purpose and can be combined for maximum flexibility. + +--- + +## State System + +The state system allows elements to have conditional states that automatically activate based on custom logic. This is the foundation that powers the responsive plugin. + +### Conditional States + +Register states that evaluate conditions and automatically activate/deactivate: + +```lua +-- Using a function +-- When using functions, you need to manually specify which properties to observe +local label = main:addLabel() + :registerResponsiveState("small", function(self) + return self.parent:getWidth() < 25 + end, { + observe = { + {main, "width"} -- Format: {element, "property"} + }, + }) + :setTextState("small", "Compact") + :setText("Full Text") + +-- Using a string expression (automatic dependency detection) +-- String expressions automatically detect and observe referenced properties +local sidebar = main:addFrame() + :registerResponsiveState("collapsed", "parent.width < 30", 100) + :setWidthState("collapsed", 5) + :setWidthState("default", 15) + :setY(15) +``` + +### String Expressions + +String expressions automatically parse and detect dependencies - no manual `observe` needed: + +```lua +-- Dependencies are auto-detected from the expression +element:registerResponsiveState("portrait", "parent.width < parent.height", 100) +``` + +You can optionally add extra dependencies that aren't in the expression: + +```lua +-- Auto-detects parent.width, but also observes otherElement.text +element:registerResponsiveState("complex", "parent.width < 30", { + priority = 100, + observe = { + {otherElement, "text"} -- Additional dependency + } +}) +``` + +**Behind the scenes:** The system parses expressions like `"parent.width < 30"`, extracts property references (`parent.width`), and automatically sets up observers for reactive updates. + +### Manual State Control + +You can also control states manually: + +```lua +element:setState("customState", 100) -- Activate with priority 100 +element:unsetState("customState") -- Deactivate +element:hasState("customState") -- Check if active +element:getActiveStates() -- Get all active states sorted by priority +``` + +--- + +## Responsive Plugin + +The responsive plugin builds on the state system to create layouts that adapt to parent size or other conditions. It provides a fluent builder API. + +### Basic Responsive Layout + +```lua +local sidebar = main:addFrame() + :responsive() + :when("parent.width < 15") + :apply({ width = 10, background = colors.gray }) + :when("parent.width >= 40") + :apply({ width = 25, background = colors.lightGray }) + :otherwise({ width = 15 }) +``` + +### Advanced Conditions + +String expressions support math operations and can reference any element property: + +```lua +local dynamicFrame = frame:addFrame() + :responsive() + :when("parent.width < parent.height") + :apply({ width = "parent.width * 0.9", height = 10 }) + :otherwise({ width = 20, height = "parent.height * 0.9" }) +``` + +**Behind the scenes:** The responsive plugin automatically detects dependencies from expressions (like `parent.width`) and sets up observers for reactive updates. + +--- + +## Reactive Plugin + +The reactive plugin allows property values themselves to be dynamic expressions that automatically update when dependencies change. + +### Basic Usage + +```lua +-- Center a label horizontally +local label = frame:addLabel() + :setText("Centered") + :setX("{parent.width / 2 - self.width / 2}") + +-- Progress bar that takes 80% of parent width +frame:addProgressBar() + :setWidth("{parent.width * 0.8}") + :setX("{parent.width * 0.1}") +``` + +### Available Variables + +- `self` - The current element +- `parent` - The parent element +- `elementName` - Named elements (e.g., `mySlider.value`) + +### Linked Properties + +```lua +local slider = frame:addSlider("volumeSlider") + :setPosition(2, 2) + +local label = frame:addLabel() + :setText("{volumeSlider.value}") -- Text updates with slider + :setX("{volumeSlider.x + volumeSlider.width + 2}") +``` + +### Dynamic Sizing + +```lua +local label = frame:addLabel() + :setText("Dynamic width") + :setWidth("{#self.text + 2}") -- Width = text length + padding +``` + +--- + +### Combining Systems + +You can combine all three systems for maximum flexibility: + +```lua +local frame = main:addFrame() + :setWidth("{parent.width * 0.8}") -- Reactive: 80% of parent + :responsive() -- Responsive: breakpoints + :when("parent.width < 30") + :apply({ background = colors.gray }) + :otherwise({ background = colors.lightGray }) + :setPropertyState("background", "hover", colors.white) -- State: hover effect +``` + +The responsive and reactive plugins work together - you can use reactive expressions within `:apply()`: + +```lua +local element = main:addLabel() + :responsive() + :when("parent.width < 30") + :apply({ text = "Small", x = "{parent.width - self.width}" }) -- Reactive expression + :otherwise({ text = "Large", x = 5 }) +``` + +--- + +## Practical Example: Adaptive Layout + +A common use case is creating a layout that adapts between side-by-side and stacked views based on screen width: + +```lua run +local basalt = require("basalt") +local main = basalt.getMainFrame() + +-- Left container +local rightContainer = main:addFrame() + :setSize(20, 10) + :setBackground(colors.green) + :responsive() + :when("parent.width >= 45") -- Wide: positioned next to left + :apply({ + x = 24, + y = 2, + width = 20 + }) + :otherwise({ + x = 2, + y = 13, + width = "{parent.width - 3}" + }) -- Narrow: positioned below left + :done() + +rightContainer:addLabel() + :setText("Right Panel") + :setPosition(2, 2) + +-- Status label showing current layout mode +local statusLabel = main:addLabel() + :setPosition(2, 24) + :responsive() + :when("parent.width >= 45") + :apply({ text = "Layout: Side by Side", foreground = colors.lime }) + :otherwise({ text = "Layout: Stacked", foreground = colors.orange }) + :done() +basalt.run() +``` + +When the main frame is wide (≥45 characters), the containers appear side by side. When it's narrow, they stack vertically - and the status label updates to reflect the current mode.