anima-core 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.reek.yml +27 -1
- data/CHANGELOG.md +4 -0
- data/README.md +219 -25
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +76 -28
- data/app/jobs/agent_request_job.rb +24 -0
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +1 -1
- data/app/models/concerns/event/broadcasting.rb +20 -2
- data/app/models/event.rb +1 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +347 -22
- data/config/application.rb +2 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -9
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +4 -0
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +8 -7
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +3 -4
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +15 -16
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tui/app.rb +332 -43
- data/lib/tui/message_store.rb +20 -0
- data/lib/tui/screens/chat.rb +207 -20
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +284 -1
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
# Layout System
|
|
2
|
+
|
|
3
|
+
RatatuiRuby's constraint-based layout divides screen areas dynamically, adapting to terminal resizing for responsive TUI designs.
|
|
4
|
+
|
|
5
|
+
## Core Components
|
|
6
|
+
|
|
7
|
+
- **Layout** - Divides areas using constraints
|
|
8
|
+
- **Constraint** - Sizing rules for sections
|
|
9
|
+
- **Rect** - Rectangular screen areas
|
|
10
|
+
|
|
11
|
+
## Layout Split
|
|
12
|
+
|
|
13
|
+
Primary method for dividing areas:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
# Using TUI factory (recommended)
|
|
17
|
+
top, bottom = tui.split(
|
|
18
|
+
frame.area,
|
|
19
|
+
direction: :vertical,
|
|
20
|
+
constraints: [
|
|
21
|
+
tui.constraint_percentage(75),
|
|
22
|
+
tui.constraint_percentage(25)
|
|
23
|
+
]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Full signature
|
|
27
|
+
tui.layout_split(
|
|
28
|
+
area, # Rect to split
|
|
29
|
+
direction: :vertical, # :vertical or :horizontal
|
|
30
|
+
constraints: [], # Array of constraints
|
|
31
|
+
flex: :legacy # Flex algorithm
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Returns:** `Array<Rect>` of computed sections.
|
|
36
|
+
|
|
37
|
+
## Direction
|
|
38
|
+
|
|
39
|
+
### Vertical (Top to Bottom)
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
header, content, footer = tui.split(
|
|
43
|
+
frame.area,
|
|
44
|
+
direction: :vertical,
|
|
45
|
+
constraints: [
|
|
46
|
+
tui.constraint_length(3), # Top header
|
|
47
|
+
tui.constraint_fill(1), # Expanding content
|
|
48
|
+
tui.constraint_length(1) # Bottom status
|
|
49
|
+
]
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Horizontal (Left to Right)
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
sidebar, main = tui.split(
|
|
57
|
+
frame.area,
|
|
58
|
+
direction: :horizontal,
|
|
59
|
+
constraints: [
|
|
60
|
+
tui.constraint_length(20), # Fixed sidebar
|
|
61
|
+
tui.constraint_min(0) # Expanding main
|
|
62
|
+
]
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Constraints
|
|
67
|
+
|
|
68
|
+
### Length (Fixed Size)
|
|
69
|
+
|
|
70
|
+
Exact, immutable size:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
tui.constraint_length(10)
|
|
74
|
+
tui.fixed(10) # CSS-style alias
|
|
75
|
+
|
|
76
|
+
# Always 10 cells regardless of available space
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Use:** Fixed headers, footers, sidebars.
|
|
80
|
+
|
|
81
|
+
### Percentage
|
|
82
|
+
|
|
83
|
+
Proportional share of available space:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
tui.constraint_percentage(50)
|
|
87
|
+
tui.percent(50) # CSS-style alias
|
|
88
|
+
|
|
89
|
+
# 50% of available space
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Use:** Proportional layouts, split screens.
|
|
93
|
+
|
|
94
|
+
### Min (Minimum)
|
|
95
|
+
|
|
96
|
+
At least N cells, grows if space permits:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
tui.constraint_min(10)
|
|
100
|
+
|
|
101
|
+
# Enforces minimum, can expand beyond
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Use:** Sections needing minimum space but can expand.
|
|
105
|
+
|
|
106
|
+
### Max (Maximum)
|
|
107
|
+
|
|
108
|
+
Upper bound on section size:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
tui.constraint_max(20)
|
|
112
|
+
|
|
113
|
+
# Never exceeds 20 cells
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Use:** Limiting size while allowing responsiveness.
|
|
117
|
+
|
|
118
|
+
### Ratio
|
|
119
|
+
|
|
120
|
+
Exact fractional allocation:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
tui.constraint_ratio(1, 3) # One-third
|
|
124
|
+
|
|
125
|
+
# Golden ratio
|
|
126
|
+
constraints = [
|
|
127
|
+
tui.constraint_ratio(1, 3),
|
|
128
|
+
tui.constraint_ratio(2, 3)
|
|
129
|
+
]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Use:** Mathematical proportions, aspect ratios.
|
|
133
|
+
|
|
134
|
+
### Fill (Flexible)
|
|
135
|
+
|
|
136
|
+
Distributes remaining space after satisfying strict rules:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
tui.constraint_fill(1)
|
|
140
|
+
tui.flex(2) # Flexbox alias
|
|
141
|
+
tui.fr(1) # CSS Grid fraction
|
|
142
|
+
|
|
143
|
+
# Weighted distribution
|
|
144
|
+
constraints = [
|
|
145
|
+
tui.constraint_length(20), # Fixed 20
|
|
146
|
+
tui.constraint_fill(1), # Gets 1x remaining
|
|
147
|
+
tui.constraint_fill(2) # Gets 2x remaining
|
|
148
|
+
]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Use:** Flexible sections that grow.
|
|
152
|
+
|
|
153
|
+
### Batch Creation
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
# Full path: RatatuiRuby::Layout::Constraint
|
|
157
|
+
Constraint = RatatuiRuby::Layout::Constraint
|
|
158
|
+
|
|
159
|
+
Constraint.from_lengths([10, 20, 10])
|
|
160
|
+
Constraint.from_percentages([25, 50, 25])
|
|
161
|
+
Constraint.from_mins([5, 10, 5])
|
|
162
|
+
Constraint.from_maxes([20, 30, 40])
|
|
163
|
+
Constraint.from_ratios([[1, 4], [2, 4], [1, 4]])
|
|
164
|
+
Constraint.from_fills([1, 2, 1])
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Polymorphic Factory
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
tui.constraint(:length, 10)
|
|
171
|
+
tui.constraint(:percentage, 50)
|
|
172
|
+
tui.constraint(:min, 10)
|
|
173
|
+
tui.constraint(:max, 20)
|
|
174
|
+
tui.constraint(:fill, 1)
|
|
175
|
+
tui.constraint(:ratio, 1, 3)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Flex Options
|
|
179
|
+
|
|
180
|
+
Controls extra space distribution:
|
|
181
|
+
|
|
182
|
+
| Option | Behavior |
|
|
183
|
+
|--------|----------|
|
|
184
|
+
| `:legacy` | Default |
|
|
185
|
+
| `:start` | Pack at beginning |
|
|
186
|
+
| `:end` | Pack at end |
|
|
187
|
+
| `:center` | Center sections |
|
|
188
|
+
| `:space_between` | Space between sections |
|
|
189
|
+
| `:space_around` | Space around sections |
|
|
190
|
+
| `:space_evenly` | Evenly distribute all space |
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
tui.split(
|
|
194
|
+
frame.area,
|
|
195
|
+
direction: :vertical,
|
|
196
|
+
constraints: [tui.constraint_length(10)],
|
|
197
|
+
flex: :center
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Rect Class
|
|
202
|
+
|
|
203
|
+
Encapsulates terminal grid geometry.
|
|
204
|
+
|
|
205
|
+
### Constructor
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
rect = tui.rect(x: 10, y: 5, width: 50, height: 15)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Properties
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
rect.x # Column index of top-left
|
|
215
|
+
rect.y # Row index of top-left
|
|
216
|
+
rect.width # Width in characters
|
|
217
|
+
rect.height # Height in rows
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Geometry Queries
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
rect.area # width × height
|
|
224
|
+
rect.empty? # True if width or height is zero
|
|
225
|
+
rect.contains?(15, 8) # Point-in-rectangle hit testing
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Boundary Accessors
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
rect.left # Left edge x
|
|
232
|
+
rect.right # Right edge x
|
|
233
|
+
rect.top # Top edge y
|
|
234
|
+
rect.bottom # Bottom edge y
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Transformations
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
moved = rect.offset(10, 5) # Translate
|
|
241
|
+
resized = rect.resize(new_size) # Change dimensions
|
|
242
|
+
inner = rect.inner(margin) # Shrink by margin
|
|
243
|
+
outer = rect.outer(margin) # Expand by margin
|
|
244
|
+
clamped = rect.clamp(other_rect) # Constrain to bounds
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Centering
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
screen = frame.area
|
|
251
|
+
|
|
252
|
+
# Center both axes
|
|
253
|
+
dialog = screen.centered(
|
|
254
|
+
tui.constraint_length(40),
|
|
255
|
+
tui.constraint_length(20)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Single axis
|
|
259
|
+
centered_h = screen.centered_horizontally(tui.constraint_length(40))
|
|
260
|
+
centered_v = screen.centered_vertically(tui.constraint_length(20))
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Set Operations
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
overlap = rect1.intersection(rect2) # Returns nil if disjoint
|
|
267
|
+
rect1.intersects?(rect2) # Check overlap
|
|
268
|
+
bounding = rect1.union(rect2) # Smallest enclosing box
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Iteration
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
rect.rows { |row_rect| } # Each row (height=1)
|
|
275
|
+
rect.columns { |col_rect| } # Each column (width=1)
|
|
276
|
+
rect.positions { |x, y| } # All cells, row-major
|
|
277
|
+
|
|
278
|
+
# Destructuring
|
|
279
|
+
x, y, width, height = rect
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Nested Layouts
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
# Level 1: sidebar and content
|
|
286
|
+
sidebar, content = tui.split(
|
|
287
|
+
frame.area,
|
|
288
|
+
direction: :horizontal,
|
|
289
|
+
constraints: [
|
|
290
|
+
tui.constraint_length(20),
|
|
291
|
+
tui.constraint_min(0)
|
|
292
|
+
]
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Level 2: header and body within content
|
|
296
|
+
header, body = tui.split(
|
|
297
|
+
content,
|
|
298
|
+
direction: :vertical,
|
|
299
|
+
constraints: [
|
|
300
|
+
tui.constraint_length(3),
|
|
301
|
+
tui.constraint_fill(1)
|
|
302
|
+
]
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Level 3: columns within body
|
|
306
|
+
left, right = tui.split(
|
|
307
|
+
body,
|
|
308
|
+
direction: :horizontal,
|
|
309
|
+
constraints: [
|
|
310
|
+
tui.constraint_percentage(50),
|
|
311
|
+
tui.constraint_percentage(50)
|
|
312
|
+
]
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Render to computed areas
|
|
316
|
+
frame.render_widget(sidebar_widget, sidebar)
|
|
317
|
+
frame.render_widget(header_widget, header)
|
|
318
|
+
frame.render_widget(left_widget, left)
|
|
319
|
+
frame.render_widget(right_widget, right)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Common Patterns
|
|
323
|
+
|
|
324
|
+
### Responsive Dashboard
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
header, main, footer = tui.split(
|
|
328
|
+
frame.area,
|
|
329
|
+
direction: :vertical,
|
|
330
|
+
constraints: [
|
|
331
|
+
tui.constraint_length(1), # Fixed header
|
|
332
|
+
tui.constraint_fill(1), # Expanding main
|
|
333
|
+
tui.constraint_length(1) # Fixed status
|
|
334
|
+
]
|
|
335
|
+
)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Three-Column Layout
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
sidebar, content, tools = tui.split(
|
|
342
|
+
frame.area,
|
|
343
|
+
direction: :horizontal,
|
|
344
|
+
constraints: [
|
|
345
|
+
tui.constraint_percentage(20),
|
|
346
|
+
tui.constraint_percentage(60),
|
|
347
|
+
tui.constraint_percentage(20)
|
|
348
|
+
]
|
|
349
|
+
)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Centered Modal Dialog
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
dialog = frame.area.centered(
|
|
356
|
+
tui.constraint_length(60), # 60 chars wide
|
|
357
|
+
tui.constraint_length(20) # 20 rows tall
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
frame.render_widget(modal_widget, dialog)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Even Distribution
|
|
364
|
+
|
|
365
|
+
```ruby
|
|
366
|
+
buttons = tui.split(
|
|
367
|
+
control_area,
|
|
368
|
+
direction: :horizontal,
|
|
369
|
+
constraints: RatatuiRuby::Layout::Constraint.from_fills([1, 1, 1, 1]),
|
|
370
|
+
flex: :space_evenly
|
|
371
|
+
)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Cached Layout for Mouse Handling
|
|
375
|
+
|
|
376
|
+
Compute during rendering, reuse for event handling:
|
|
377
|
+
|
|
378
|
+
```ruby
|
|
379
|
+
class App
|
|
380
|
+
attr_accessor :button_area
|
|
381
|
+
|
|
382
|
+
def draw(frame, tui)
|
|
383
|
+
@button_area = frame.area.centered(
|
|
384
|
+
tui.constraint_length(20),
|
|
385
|
+
tui.constraint_length(3)
|
|
386
|
+
)
|
|
387
|
+
frame.render_widget(button, @button_area)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def handle_event(event)
|
|
391
|
+
if event.mouse? && @button_area.contains?(event.x, event.y)
|
|
392
|
+
handle_button_click
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Factory Methods Summary
|
|
399
|
+
|
|
400
|
+
| Factory | Purpose |
|
|
401
|
+
|---------|---------|
|
|
402
|
+
| `tui.split(area, direction:, constraints:, flex:)` | Split area |
|
|
403
|
+
| `tui.rect(x:, y:, width:, height:)` | Create Rect |
|
|
404
|
+
| `tui.constraint_length(n)` / `tui.fixed(n)` | Fixed size |
|
|
405
|
+
| `tui.constraint_percentage(n)` / `tui.percent(n)` | Proportional |
|
|
406
|
+
| `tui.constraint_min(n)` | Minimum |
|
|
407
|
+
| `tui.constraint_max(n)` | Maximum |
|
|
408
|
+
| `tui.constraint_ratio(n, d)` | Fractional |
|
|
409
|
+
| `tui.constraint_fill(n)` / `tui.flex(n)` / `tui.fr(n)` | Flexible |
|
|
410
|
+
| `tui.constraint(type, ...)` | Polymorphic |
|
|
411
|
+
|
|
412
|
+
## Best Practices
|
|
413
|
+
|
|
414
|
+
1. **Use TUI factories** for cleaner code
|
|
415
|
+
2. **Compute layouts fresh each frame** for responsive resizing
|
|
416
|
+
3. **Cache Rects** when needed for mouse hit testing
|
|
417
|
+
4. **Start with vertical splits** for top-level structure
|
|
418
|
+
5. **Use Fill** for flexible sections that should grow
|
|
419
|
+
6. **Use Length** for fixed UI elements
|
|
420
|
+
7. **Use Percentage/Ratio** for proportional layouts
|
|
421
|
+
8. **Leverage Rect.centered()** for common positioning
|
|
422
|
+
9. **Apply margins via Block borders** rather than layout level
|
|
423
|
+
10. **Keep layouts immutable** - create new rather than modify
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Styling System
|
|
2
|
+
|
|
3
|
+
RatatuiRuby provides colors, modifiers, and hierarchical text composition for visually appealing terminal interfaces.
|
|
4
|
+
|
|
5
|
+
## Style Class
|
|
6
|
+
|
|
7
|
+
### Properties
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
Style::Style.new(
|
|
11
|
+
fg: nil, # Foreground color
|
|
12
|
+
bg: nil, # Background color
|
|
13
|
+
underline_color: nil, # Underline color
|
|
14
|
+
modifiers: [], # Text effects to apply
|
|
15
|
+
remove_modifiers: [] # Effects to remove from inherited styles
|
|
16
|
+
)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Creating Styles
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
# Full constructor
|
|
23
|
+
Style::Style.new(fg: :red, bg: :white, modifiers: [:bold])
|
|
24
|
+
|
|
25
|
+
# Convenience method
|
|
26
|
+
Style::Style.with(fg: "#ff00ff")
|
|
27
|
+
|
|
28
|
+
# Empty style
|
|
29
|
+
Style::Style.default
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Factory Method
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
tui.style(fg: :red, modifiers: [:bold])
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Color Options
|
|
39
|
+
|
|
40
|
+
### Named Colors (Symbols)
|
|
41
|
+
|
|
42
|
+
**Standard:** `:black`, `:red`, `:green`, `:yellow`, `:blue`, `:magenta`, `:cyan`, `:white`
|
|
43
|
+
|
|
44
|
+
**Light variants:** `:light_red`, `:light_green`, `:light_yellow`, `:light_blue`, `:light_magenta`, `:light_cyan`
|
|
45
|
+
|
|
46
|
+
**Gray variants:** `:gray`, `:dark_gray`
|
|
47
|
+
|
|
48
|
+
**Special:** `:reset` — Terminal default
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
tui.style(fg: :cyan, bg: :black)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Indexed Colors (0-255)
|
|
55
|
+
|
|
56
|
+
Xterm 256-color palette:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
tui.style(fg: 196) # Bright red
|
|
60
|
+
tui.style(bg: 240) # Dark gray
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### True Color (Hex)
|
|
64
|
+
|
|
65
|
+
24-bit color via `"#RRGGBB"`:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
tui.style(fg: "#ff00ff") # Magenta
|
|
69
|
+
tui.style(bg: "#1a1a1a") # Near-black
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Color Factory Methods
|
|
73
|
+
|
|
74
|
+
Convert colors from design tools:
|
|
75
|
+
|
|
76
|
+
### Hex Conversion
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
Color.from_u32(0xFF0000) # => "#ff0000"
|
|
80
|
+
Color.hex(0x00FF00) # => "#00ff00"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### HSL Conversion
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
Color.from_hsl(0, 100, 50) # Hue 0-360, Sat 0-100, Light 0-100
|
|
87
|
+
Color.hsl(120, 100, 50) # => "#00ff00" (green)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### HSLuv (Perceptually Uniform)
|
|
91
|
+
|
|
92
|
+
Equal lightness values appear equally bright:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
Color.from_hsluv(12.18, 100, 53.2) # => "#ff0000"
|
|
96
|
+
Color.hsluv(-94.13, 100, 32.3) # => "#0000ff"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Text Modifiers
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
tui.style(modifiers: [:bold, :underlined])
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
| Modifier | Effect |
|
|
106
|
+
|----------|--------|
|
|
107
|
+
| `:bold` | Bold weight |
|
|
108
|
+
| `:dim` | Reduced brightness |
|
|
109
|
+
| `:italic` | Italicized |
|
|
110
|
+
| `:underlined` | Underline |
|
|
111
|
+
| `:slow_blink` | Slow blink |
|
|
112
|
+
| `:rapid_blink` | Fast blink |
|
|
113
|
+
| `:reversed` | Swap fg/bg |
|
|
114
|
+
| `:hidden` | Concealed text |
|
|
115
|
+
| `:crossed_out` | Line-through |
|
|
116
|
+
|
|
117
|
+
## Text Composition
|
|
118
|
+
|
|
119
|
+
### Span (Styled Fragment)
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# Create span
|
|
123
|
+
tui.text_span(content: "Bold text", style: tui.style(modifiers: [:bold]))
|
|
124
|
+
|
|
125
|
+
# Raw/unstyled
|
|
126
|
+
Text::Span.raw("Plain text")
|
|
127
|
+
|
|
128
|
+
# Styled shorthand
|
|
129
|
+
Text::Span.styled("Quick", style)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Methods:**
|
|
133
|
+
- `width()` — Unicode-aware display width
|
|
134
|
+
- `patch_style(style)` — Merge styles
|
|
135
|
+
- `reset_style()` — Remove styling
|
|
136
|
+
|
|
137
|
+
### Line (Span Collection)
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
tui.text_line(
|
|
141
|
+
spans: [span1, span2, span3],
|
|
142
|
+
alignment: :center,
|
|
143
|
+
style: base_style
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# From string
|
|
147
|
+
Text::Line.from_string("Hello")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Alignment methods:**
|
|
151
|
+
```ruby
|
|
152
|
+
line.left_aligned
|
|
153
|
+
line.centered
|
|
154
|
+
line.right_aligned
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Methods:**
|
|
158
|
+
- `push_span(span)` — Add span (returns new Line)
|
|
159
|
+
- `patch_style(style)` — Merge across all spans
|
|
160
|
+
- `width()` — Total display width
|
|
161
|
+
|
|
162
|
+
### Text Width Calculation
|
|
163
|
+
|
|
164
|
+
Unicode-aware measurement:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
tui.text_width("Hello") # => 5
|
|
168
|
+
tui.text_width("你好") # => 4 (CJK = 2 cells each)
|
|
169
|
+
tui.text_width("Hello 👍") # => 8 (emoji = 2 cells)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Hierarchical Styling
|
|
173
|
+
|
|
174
|
+
### Widget-Level
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
tui.block(
|
|
178
|
+
title: "Title",
|
|
179
|
+
title_style: tui.style(fg: :cyan, modifiers: [:bold]),
|
|
180
|
+
border_style: tui.style(fg: :magenta),
|
|
181
|
+
style: tui.style(bg: :black)
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Line-Level
|
|
186
|
+
|
|
187
|
+
Affects all contained spans:
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
tui.text_line(
|
|
191
|
+
spans: [span1, span2],
|
|
192
|
+
style: tui.style(fg: :blue)
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Span-Level
|
|
197
|
+
|
|
198
|
+
Overrides line styles:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
tui.text_span(
|
|
202
|
+
content: "Special",
|
|
203
|
+
style: tui.style(fg: :red, modifiers: [:underlined], underline_color: :red)
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Style Merging
|
|
208
|
+
|
|
209
|
+
`patch_style()` merges without replacing:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
base = Style::Style.with(fg: :blue)
|
|
213
|
+
result = span.patch_style(Style::Style.with(modifiers: [:bold]))
|
|
214
|
+
# fg remains :blue, modifiers adds [:bold]
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Hash-Based Syntax
|
|
218
|
+
|
|
219
|
+
Widgets accept styles as hashes:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
tui.paragraph(
|
|
223
|
+
text: "Content",
|
|
224
|
+
style: {fg: "green", modifiers: [:bold]},
|
|
225
|
+
block: tui.block(border_style: {fg: "cyan"})
|
|
226
|
+
)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Complete Example
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
# Create styled spans
|
|
233
|
+
normal = tui.text_span(content: "Normal, ")
|
|
234
|
+
bold = tui.text_span(
|
|
235
|
+
content: "Bold",
|
|
236
|
+
style: tui.style(modifiers: [:bold])
|
|
237
|
+
)
|
|
238
|
+
colored = tui.text_span(
|
|
239
|
+
content: " and colored",
|
|
240
|
+
style: tui.style(fg: :green, modifiers: [:italic])
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Compose into centered line
|
|
244
|
+
line = tui.text_line(spans: [normal, bold, colored]).centered
|
|
245
|
+
|
|
246
|
+
# Use in widget
|
|
247
|
+
tui.paragraph(
|
|
248
|
+
text: [line],
|
|
249
|
+
block: tui.block(
|
|
250
|
+
title: "Styled Text",
|
|
251
|
+
title_style: tui.style(fg: :cyan, modifiers: [:bold]),
|
|
252
|
+
border_style: tui.style(fg: :magenta),
|
|
253
|
+
borders: [:all]
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Quick Reference
|
|
259
|
+
|
|
260
|
+
| Factory | Creates |
|
|
261
|
+
|---------|---------|
|
|
262
|
+
| `tui.style(fg:, bg:, modifiers:)` | Style object |
|
|
263
|
+
| `tui.text_span(content:, style:)` | Styled text fragment |
|
|
264
|
+
| `tui.text_line(spans:, alignment:)` | Line of spans |
|
|
265
|
+
| `tui.text_width(string)` | Display width |
|
|
266
|
+
| `Color.hex(0xRRGGBB)` | Hex color string |
|
|
267
|
+
| `Color.hsl(h, s, l)` | HSL to hex |
|
|
268
|
+
| `Color.hsluv(h, s, l)` | Perceptually uniform HSL |
|