1. Go to this page and download the library: Download yangweijie/perry-php library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
yangweijie / perry-php example snippets
use Perry\App;
use Perry\UI\Binding;
use Perry\UI\Styling\Style;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;
$count = new Binding('count', 0);
$app = new App();
$app->setRoot(
new VStack(
(new Text($count))->style(Style::make()->fontSize(48)),
new HStack(
(new Button('-', function () use ($count) {
$count -= 1;
}))->style(Style::make()->fontSize(24)->padding(16)),
(new Button('+', function () use ($count) {
$count += 1;
}))->style(Style::make()->fontSize(24)->padding(16)),
),
)
);
// Generate for any platform
echo $app->generateCode('swiftui'); // macOS/iOS → SwiftUI Swift
echo $app->generateCode('html'); // Web → HTML/CSS/JS
echo $app->generateCode('compose'); // Android → Jetpack Compose
use Perry\App;
use Perry\UI\Binding;
use Perry\UI\Styling\Style;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;
$app = new App();
$root = new VStack(
(new Text('Hello, Perry!'))->style(Style::make()->fontSize(24)),
new HStack(
(new Button('Click Me'))
->style(Style::make()->backgroundColor('#007AFF')->foregroundColor('#fff')),
(new Button('Cancel'))
->style(Style::make()->backgroundColor('#f0f0f0')),
),
);
$app->setRoot($root);
echo $app->generateCode('swiftui');
use Perry\UI\Binding;
use Perry\UI\Widget\Text;
// Static text — renders literal string
$title = new Text('Hello, World!');
// Reactive text — renders binding value, auto-updates when state changes
$display = new Binding('display', '0');
$counter = new Text($display);
use Perry\App;
use Perry\UI\AppContainer;
use Perry\UI\Binding;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;
$time = new Binding('time', '00:00:00');
$date = new Binding('date', '2024-01-01');
$app = new App();
$app->setRoot(
new AppContainer(
new VStack(
(new Text($time))->style(
\Perry\UI\Styling\Style::make()->fontSize(32)->textAlignment('center')
),
(new Text($date))->style(
\Perry\UI\Styling\Style::make()->fontSize(16)->foregroundColor('#888')
),
),
320, 200, // window size
$date, // extra binding (not attached to a Text widget)
)
);
echo $app->generateCode('html');
// Generates: const state = { time: "00:00:00", date: "2024-01-01" };
// function render() { el_time.textContent = state.time; ... }
use Perry\UI\Action;
use Perry\UI\Binding;
use Perry\UI\Widget\Button;
use Perry\UI\Styling\Style;
$display = new Binding('display', '0');
// 1. Static button — no action
$ok = new Button('OK');
// 2. Simple action — set binding to value
$setZero = new Button('Reset', Action::set($display, '0'));
// 3. Append action — append string to binding
$addDigit = new Button('1', Action::append($display, '1'));
// 4. Closure action — full PHP logic → cross-platform code
$toggleSign = new Button('±', Action::fromClosure(function () use ($display) {
if ($display[0] === '-') {
$display = substr($display, 1);
} else {
$display = '-' . $display;
}
}));
// 5. Closure with bindings — pass external values into closure
$button = new Button('×', Action::fromClosure(
function () use ($display, $operand1, $operation) {
$operand1 = floatval($display);
$operation = '×';
$display .= '×';
},
compact('operand1', 'operation') // external bindings for replacement
));
// Styled button
$styled = (new Button('Submit', $toggleSign))
->style(Style::make()
->backgroundColor('#007AFF')
->foregroundColor('#ffffff')
->fontSize(18)
->padding(12)
->cornerRadius(8)
);
use Perry\UI\Widget\VStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\Button;
// Pass children as constructor arguments
$layout = new VStack(
new Text('Header'),
new Text('Body content goes here'),
new Text('Footer'),
);
use Perry\UI\Styling\Style;
$spaced = (new VStack(
new Text('A'),
new Text('B'),
new Text('C'),
))->style(Style::make()->padding(16)); // 16px spacing between children
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\Spacer;
// Button row
$toolbar = new HStack(
(new Button('Bold'))->style(Style::make()->fontSize(14)),
(new Button('Italic'))->style(Style::make()->fontSize(14)),
(new Button('Underline'))->style(Style::make()->fontSize(14)),
);
// Left-right layout with Spacer
$navbar = new HStack(
new Text('Logo'),
new Spacer(), // pushes "Menu" to the right
new Text('Menu'),
);
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\Spacer;
// "Left" is on the left, "Right" is on the right, Spacer fills the gap
$row = new HStack(
new Text('Left'),
new Spacer(),
new Text('Right'),
);
// Vertical: pushes "Top" and "Bottom" apart
$column = new VStack(
new Text('Top'),
new Spacer(),
new Text('Bottom'),
);
use Perry\UI\Widget\Image;
// Local file
$logo = new Image('logo.png');
// Named asset
$avatar = new Image('avatar');
use Perry\UI\Widget\ScrollView;
use Perry\UI\Widget\VStack;
use Perry\UI\Widget\Text;
$list = new ScrollView(
new VStack(
new Text('Item 1'),
new Text('Item 2'),
new Text('Item 3'),
// ... many items
)
);
use Perry\UI\State;
use Perry\UI\Widget\TextInput;
$state = new State();
$name = $state->create(''); // initial value: empty string
$input = new TextInput($name, 'Enter your name...');
use Perry\UI\State;
use Perry\UI\Widget\Toggle;
$state = new State();
$darkMode = $state->create(false);
$toggle = new Toggle($darkMode, 'Dark Mode');
use Perry\UI\AppContainer;
use Perry\UI\Binding;
use Perry\UI\Widget\VStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\Button;
use Perry\UI\Action;
use Perry\UI\Styling\Style;
$count = new Binding('count', 0);
$label = new Binding('label', 'Clicks: 0');
$app = new AppContainer(
// 1. Content widget tree
new VStack(
(new Text($label))->style(Style::make()->fontSize(24)),
(new Button('Increment', function () use ($count, $label) {
$count += 1;
$label = 'Clicks: ' . strval($count);
}))->style(Style::make()->backgroundColor('#007AFF')->foregroundColor('#fff')),
),
// 2. Window size (optional)
320,
480,
// 3. Extra bindings not attached to any Text widget
$count,
);
$app2 = new App();
$app2->setRoot($app);
echo $app2->generateCode('html');
use Perry\UI\Binding;
$count = new Binding('count', 0); // int
$display = new Binding('display', '0'); // string
$visible = new Binding('visible', true); // bool
$opacity = new Binding('opacity', 1.0); // float
$count = new Binding('count', 0);
// In a closure action, $count refers to the binding name in generated code
// The closure gets parsed → IR → target language assignment
$action = Action::fromClosure(function () use ($count) {
$count += 1;
});
// Generated Swift: count = count + 1
// Generated JS: state.count = state.count + 1
// Generated Kotlin: count.value = count.value + 1
use Perry\UI\State;
$state = new State();
// Create state entries
$name = $state->create(''); // StateId
$darkMode = $state->create(false); // StateId
$speed = $state->create(1.0); // StateId
// Read values
$currentName = $state->get($name); // ''
// Update values
$state->set($name, 'Alice');
// Subscribe to changes (runtime only, not code-generated)
$state->subscribe($name, function (mixed $newValue) {
echo "Name changed to: $newValue\n";
});
use Perry\UI\Action;
use Perry\UI\Binding;
$display = new Binding('display', '0');
// SetValue — assign a value
$action = Action::set($display, '42');
$action = Action::set($display, true); // bool
$action = Action::set($display, 3.14); // float
// Append — concatenate a string
$action = Action::append($display, '1'); // display += "1"
// Clear — reset to initial value
$action = Action::clear($display); // display = "0"
// Custom — raw platform-specific code
$action = Action::custom('display.text = ""'); // passed through as-is
use Perry\UI\Action;
use Perry\UI\Binding;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\Slider;
use Perry\UI\Widget\TextInput;
use Perry\UI\Widget\Toggle;
$display = new Binding('display', '0');
$operand1 = new Binding('operand1', 0.0);
$operation = new Binding('operation', '');
// Button — action on click
$btn = new Button('7', Action::append($display, '7'));
// Slider — action on value change
$slider = new Slider(0, 100, $operand1, onChange: Action::set($operand1, 50));
// TextInput — action on text change
$input = new TextInput($display, onChange: Action::set($display, ''));
// Toggle — action on toggle
$toggle = new Toggle(true, onToggle: Action::set($display, 'toggled'));
use Perry\UI\Action;
use Perry\UI\Binding;
$display = new Binding('display', '0');
$operand1 = new Binding('operand1', 0.0);
$operation = new Binding('operation', '');
$action = Action::fromClosure(
function () use ($display, $operand1, $operation) {
$operand1 = floatval($display);
$operation = '+';
$display .= '+';
}
);
// Pass external values into the closure at definition time
$action = Action::fromClosure(
function () use ($display, $digit) {
$display .= $digit;
},
['digit' => '5'] // $digit is replaced with "5" in generated code
);
// Generated Swift: display = display + "5"
// Generated JS: state.display = state.display + "5"
function numBtn(string $digit, Binding $display): Button {
return new Button($digit, Action::fromClosure(
function () use ($digit, $display) {
$display .= $digit;
},
compact('digit')
));
}
$row = new HStack(
numBtn('1', $display),
numBtn('2', $display),
numBtn('3', $display),
);
use Perry\UI\Styling\StyleMatrix;
use Perry\UI\Styling\StyleProperty;
$matrix = new StyleMatrix();
// Check a specific property on a platform
$support = $matrix->getSupport('macos', StyleProperty::CornerRadius);
// PlatformSupport::Wired (fully supported)
// Get all supported properties for a platform
$wired = $matrix->getWiredProperties('macos');
// Check if a platform has full support
$full = $matrix->isFullySupported('macos'); // bool
// Get missing properties
$missing = $matrix->getMissingProperties('android');
use Perry\App;
use Perry\Build\Target;
$app = new App();
$app->setRoot($widgetTree);
// By name
echo $app->generateCode('swiftui');
echo $app->generateCode('html');
// Auto-detect from target
$app = new App(Target::fromString('macos'));
echo $app->generateForTarget();
// Write to file
$backend = $app->codegen()->get('html');
$backend->generateToFile($widgetTree, 'build/output.html');
use Perry\Generator\SwiftGenerator;
use Perry\IR\Assignment;
use Perry\IR\Literal;
$gen = new SwiftGenerator(stateVars: ['display', 'result']);
$ir = new Assignment('display', new Literal('Hello'));
echo $gen->generateAssignment($ir);
// Output: display = "Hello"
$ir2 = new Assignment('count', new Literal(42));
echo $gen->generateAssignment($ir2);
// Output: var count = 42 (new variable, not a state var)
use Perry\Build\Target;
$target = Target::detect(); // auto-detect current platform
$target = Target::fromString('macos'); // from string
$target->isApple(); // true for macOS, iOS, tvOS, visionOS, watchOS
$target->isDesktop(); // true for macOS, Linux, Windows
$target->isMobile(); // true for iOS, Android, watchOS
use Perry\App;
use Perry\Build\Target;
use Perry\UI\Action;
use Perry\UI\AppContainer;
use Perry\UI\Binding;
use Perry\UI\Styling\Style;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;
// State
$display = new Binding('display', '0');
$result = new Binding('result', '');
$operand1 = new Binding('operand1', 0.0);
$operand2 = new Binding('operand2', 0.0);
$operation = new Binding('operation', '');
$isTyping = new Binding('isTyping', false);
$typed = new Binding('typed', '');
// Styles
$numberBtn = Style::make()->fontSize(24)->backgroundColor('#f0f0f0')->padding(20)->cornerRadius(8);
$opBtn = Style::make()->fontSize(24)->backgroundColor('#FF9500')->foregroundColor('#fff')->padding(20)->cornerRadius(8);
// Digit button factory
function numBtn(string $d, Binding $display, Binding $op2, Binding $typing, Binding $typed, Binding $op): Button {
return (new Button($d, Action::fromClosure(
function () use ($d, $display, $op2, $typing, $typed, $op) {
if ($typing) {
$typed .= $d;
$display .= $d;
} else {
$typed = $d;
$display = $d;
$typing = true;
}
$op2 = floatval($typed);
},
compact('d')
)))->style(Style::make()->fontSize(24)->backgroundColor('#f0f0f0')->padding(20)->cornerRadius(8));
}
// Build
$app = new App(Target::fromString('macos'));
$app->setRoot(
new AppContainer(
new VStack(
(new Text($result))->style(Style::make()->fontSize(16)->foregroundColor('#888')),
(new Text($display))->style(Style::make()->fontSize(32)->padding(16)),
new HStack(
numBtn('7', $display, $operand2, $isTyping, $typed, $operation),
numBtn('8', $display, $operand2, $isTyping, $typed, $operation),
numBtn('9', $display, $operand2, $isTyping, $typed, $operation),
(new Button('×', Action::fromClosure(function () use ($display, $operand1, $operation, $typed) {
$operand1 = floatval($typed);
$operation = '×';
$display .= '×';
$typed = '';
})))->style($opBtn),
),
// ... more rows
),
320, 480,
$operand1, $operand2, $operation, $isTyping, $typed
)
);
echo $app->generateForTarget();
use Perry\App;
use Perry\UI\Action;
use Perry\UI\Binding;
use Perry\UI\Styling\Style;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;
$count = new Binding('count', 0);
$app = new App();
$app->setRoot(
new VStack(
(new Text($count))->style(Style::make()->fontSize(48)),
new HStack(
(new Button('-', Action::fromClosure(function () use ($count) {
$count -= 1;
})))->style(Style::make()->fontSize(24)->padding(16)),
(new Button('+', Action::fromClosure(function () use ($count) {
$count += 1;
})))->style(Style::make()->fontSize(24)->padding(16)),
),
)
);
echo $app->generateCode('html');
use Perry\App;
use Perry\UI\Action;
use Perry\UI\Binding;
use Perry\UI\Styling\Style;
use Perry\UI\Widget\Button;
use Perry\UI\Widget\HStack;
use Perry\UI\Widget\ScrollView;
use Perry\UI\Widget\Text;
use Perry\UI\Widget\VStack;
use Perry\UI\Widget\AppContainer;
$items = new Binding('items', 'Buy milk');
$ newItem = new Binding('newItem', '');
$app = new App();
$app->setRoot(
new AppContainer(
new VStack(
(new Text($items))->style(Style::make()->fontSize(16)),
new HStack(
(new Button('Add', Action::fromClosure(function () use ($items, $newItem) {
$items .= "\n" . $newItem;
$newItem = '';
})))->style(Style::make()->backgroundColor('#007AFF')->foregroundColor('#fff')),
(new Button('Clear', Action::fromClosure(function () use ($items) {
$items = '';
})))->style(Style::make()->backgroundColor('#ff3b30')->foregroundColor('#fff')),
),
),
320, 480,
$newItem
)
);
echo $app->generateCode('swiftui');
declare(strict_types=1);
namespace Perry\UI\Widget;
use Perry\UI\Widget;
use Perry\UI\WidgetKind;
final class Slider extends Widget
{
public function __construct(
private float $min = 0.0,
private float $max = 1.0,
private float $step = 0.1,
private ?\Perry\UI\Binding $value = null,
) {
parent::__construct();
}
public function kind(): WidgetKind
{
return WidgetKind::Slider; // must add this enum case first
}
public function min(): float { return $this->min; }
public function max(): float { return $this->max; }
public function step(): float { return $this->step; }
public function getValue(): ?\Perry\UI\Binding { return $this->value; }
}
// src/UI/WidgetKind.php
enum WidgetKind: int
{
// ... existing cases ...
case Slider = 9;
case List = 10;
// ...
}
bash
php examples/pry.php windows --build
# Output: build/pry.exe + build/pry.html
# Also works on other platforms:
php examples/pry.php macos --build
php examples/pry.php web
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.