Skip to content

Commit 74e1d4e

Browse files
Merge pull request #12 from developersharif/docv1.5
Add documentation files and GitHub Actions workflow for deploying docs
2 parents b289609 + 2aa8e69 commit 74e1d4e

9 files changed

Lines changed: 628 additions & 0 deletions

File tree

.github/workflows/docs.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Deploy Docs
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'docs/**'
8+
9+
permissions:
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: false
16+
17+
jobs:
18+
deploy:
19+
environment:
20+
name: github-pages
21+
url: ${{ steps.deployment.outputs.page_url }}
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- uses: actions/configure-pages@v5
27+
28+
- uses: actions/upload-pages-artifact@v3
29+
with:
30+
path: docs
31+
32+
- id: deployment
33+
uses: actions/deploy-pages@v4

docs/.nojekyll

Whitespace-only changes.

docs/_coverpage.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!-- docs/_coverpage.md -->
2+
3+
# PHP GUI <small>with Tcl/Tk</small>
4+
5+
> A robust cross-platform GUI toolkit for PHP via FFI
6+
7+
- **PHP 8.1+** — Modern PHP with FFI extension
8+
- **Cross-platform** — Linux, Windows, macOS
9+
- **Zero dependencies** — Bundled Tcl/Tk libraries
10+
- **Simple API** — Intuitive widget classes
11+
12+
[Get Started](getting-started.md)
13+
[GitHub](https://github.com/developersharif/php-gui)

docs/_sidebar.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
- **Getting Started**
2+
- [Home](home.md)
3+
- [Installation](getting-started.md)
4+
- [Architecture](architecture.md)
5+
6+
- **Widgets**
7+
- [Window](Window.md)
8+
- [TopLevel](TopLevel.md)
9+
- [Button](Button.md)
10+
- [Label](Label.md)
11+
- [Input](Input.md)
12+
- [Entry](Entry.md)
13+
- [Frame](Frame.md)
14+
- [Canvas](Canvas.md)
15+
- [Menu](Menu.md)
16+
- [Menubutton](Menubutton.md)
17+
- [Checkbutton](Checkbutton.md)
18+
- [Combobox](Combobox.md)
19+
- [Message](Message.md)
20+
21+
- **Examples**
22+
- [Full Example](example.md)
23+
24+
- **Links**
25+
- [GitHub](https://github.com/developersharif/php-gui)
26+
- [Packagist](https://packagist.org/packages/developersharif/php-gui)

docs/architecture.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Architecture
2+
3+
## Overview
4+
5+
PHP GUI uses PHP's FFI (Foreign Function Interface) extension to bridge PHP code with native Tcl/Tk libraries. This enables building desktop GUI applications in pure PHP without any compiled extensions.
6+
7+
<img src="system.svg" style="width:250px" alt="System Architecture Diagram">
8+
9+
## How It Works
10+
11+
```
12+
PHP Application
13+
14+
Widget Classes (Window, Button, Label, ...)
15+
16+
ProcessTCL (FFI Singleton)
17+
18+
Native Tcl/Tk C Libraries
19+
20+
OS Window Manager
21+
```
22+
23+
### Core Components
24+
25+
#### ProcessTCL
26+
27+
The `ProcessTCL` class is the FFI singleton that:
28+
29+
- Loads the native Tcl/Tk library via FFI
30+
- Detects the platform (Linux, Windows, macOS) and loads the appropriate binaries
31+
- Executes Tcl commands from PHP
32+
- Manages a callback registry mapping unique IDs to PHP closures
33+
- Supports both Tcl 8.6 and Tcl 9.0
34+
35+
#### Application
36+
37+
The `Application` class manages the event loop:
38+
39+
- Initializes the Tcl/Tk environment
40+
- Continuously calls `update` to process Tcl events
41+
- Polls for callback triggers via temp files
42+
- Manages the application lifecycle (start/stop)
43+
44+
#### AbstractWidget
45+
46+
The base class for all widgets:
47+
48+
- Assigns unique IDs via `uniqid()`
49+
- Manages parent-child widget relationships
50+
- Provides layout methods: `pack()`, `grid()`, `place()`
51+
- Converts PHP option arrays to Tcl option strings
52+
53+
## Event Handling
54+
55+
The event system uses a temp-file bridge pattern:
56+
57+
1. A Tcl event fires (e.g., button click)
58+
2. The callback ID is written to `/tmp/phpgui_callback.txt`
59+
3. The `Application` event loop detects this file
60+
4. It looks up the PHP closure by ID and executes it
61+
5. The temp file is cleaned up
62+
63+
This approach bridges Tcl's event system with PHP's synchronous execution model.
64+
65+
## Bundled Libraries
66+
67+
The library bundles native Tcl/Tk binaries for all platforms, so no system installation is required:
68+
69+
| Platform | Libraries |
70+
|----------|-----------|
71+
| Linux | `libtcl8.6.so`, `libtk8.6.so` + X11 dependencies |
72+
| Windows | `tcl86t.dll`, `tk86t.dll` |
73+
| macOS | `libtcl9.0.dylib`, `libtk9.0.dylib` |
74+
75+
## Namespace & Autoloading
76+
77+
The project uses PSR-4 autoloading:
78+
79+
- `PhpGui\``src/`
80+
- `PhpGui\Widget\``src/Widget/`

docs/example.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Full Example
2+
3+
A complete application demonstrating multiple widgets, menus, dialogs, and event handling.
4+
5+
```php
6+
<?php
7+
require_once __DIR__ . '/vendor/autoload.php';
8+
9+
use PhpGui\Application;
10+
use PhpGui\Widget\Window;
11+
use PhpGui\Widget\Label;
12+
use PhpGui\Widget\Button;
13+
use PhpGui\Widget\Input;
14+
use PhpGui\Widget\TopLevel;
15+
use PhpGui\Widget\Menu;
16+
17+
$app = new Application();
18+
19+
// Create main window
20+
$window = new Window([
21+
'title' => 'Hello World Example in PHP',
22+
'width' => 800,
23+
'height' => 600
24+
]);
25+
26+
// Label
27+
$label = new Label($window->getId(), [
28+
'text' => 'Hello, PHP GUI World!'
29+
]);
30+
$label->pack(['pady' => 20]);
31+
32+
// Styled Button
33+
$styledButton = new Button($window->getId(), [
34+
'text' => 'Styled Button',
35+
'command' => function () use ($label) {
36+
$label->setText('Styled Button clicked!');
37+
},
38+
'bg' => 'blue',
39+
'fg' => 'white',
40+
'font' => 'Helvetica 16 bold'
41+
]);
42+
$styledButton->pack(['pady' => 10]);
43+
44+
// Input with Enter key binding
45+
$input = new Input($window->getId(), [
46+
'text' => 'Type here...',
47+
'bg' => 'lightyellow',
48+
'fg' => 'black',
49+
'font' => 'Arial 14'
50+
]);
51+
$input->pack(['pady' => 10]);
52+
53+
$input->onEnter(function () use ($input) {
54+
echo "Input: " . $input->getValue() . "\n";
55+
});
56+
57+
// Styled Label
58+
$styledLabel = new Label($window->getId(), [
59+
'text' => 'This is a styled label with custom colors',
60+
'fg' => 'white',
61+
'bg' => '#4CAF50',
62+
'font' => 'Arial 12',
63+
'padx' => 10,
64+
'pady' => 5,
65+
'relief' => 'raised'
66+
]);
67+
$styledLabel->pack(['pady' => 5]);
68+
69+
// Dynamic Label
70+
$dynamicLabel = new Label($window->getId(), [
71+
'text' => 'Dynamic Label',
72+
'font' => 'Arial 11 italic',
73+
'fg' => '#666666'
74+
]);
75+
$dynamicLabel->pack(['pady' => 5]);
76+
77+
// Update Labels Button
78+
$updateButton = new Button($window->getId(), [
79+
'text' => 'Update Labels',
80+
'command' => function () use ($dynamicLabel, $styledLabel) {
81+
$dynamicLabel->setText('Label text updated!');
82+
$dynamicLabel->setForeground('#009688');
83+
$styledLabel->setBackground('#2196F3');
84+
$styledLabel->setText('Colors and text can be changed dynamically');
85+
}
86+
]);
87+
$updateButton->pack(['pady' => 5]);
88+
89+
// --- Menus ---
90+
91+
$mainMenu = new Menu($window->getId(), ['type' => 'main']);
92+
93+
// File Menu
94+
$fileMenu = $mainMenu->addSubmenu('File');
95+
$fileMenu->addCommand('New', function () use ($dynamicLabel) {
96+
$dynamicLabel->setText('New File Selected');
97+
});
98+
$fileMenu->addCommand('Open', function () use ($dynamicLabel) {
99+
$dynamicLabel->setText('Open Selected');
100+
});
101+
$fileMenu->addSeparator();
102+
$fileMenu->addCommand('Exit', function () {
103+
exit();
104+
}, ['foreground' => 'red']);
105+
106+
// Edit Menu
107+
$editMenu = $mainMenu->addSubmenu('Edit');
108+
$editMenu->addCommand('Copy', function () use ($styledLabel) {
109+
$styledLabel->setText('Copy Selected');
110+
});
111+
$editMenu->addCommand('Paste', function () use ($styledLabel) {
112+
$styledLabel->setText('Paste Selected');
113+
});
114+
115+
// Help Menu with Nested Submenu
116+
$helpMenu = $mainMenu->addSubmenu('Help');
117+
$aboutMenu = $helpMenu->addSubmenu('About');
118+
$aboutMenu->addCommand('Version', function () use ($dynamicLabel) {
119+
$dynamicLabel->setText('Version 1.0');
120+
});
121+
122+
// --- TopLevel & Dialogs ---
123+
124+
// Open New Window
125+
$topLevelButton = new Button($window->getId(), [
126+
'text' => 'Open New Window',
127+
'command' => function () use ($dynamicLabel) {
128+
$topLevel = new TopLevel([
129+
'title' => 'New Window Example',
130+
'width' => 300,
131+
'height' => 200
132+
]);
133+
134+
$label = new Label($topLevel->getId(), [
135+
'text' => 'This is a new window',
136+
'font' => 'Arial 14'
137+
]);
138+
$label->pack(['pady' => 20]);
139+
140+
$closeBtn = new Button($topLevel->getId(), [
141+
'text' => 'Close Window',
142+
'command' => function () use ($topLevel, $dynamicLabel) {
143+
$dynamicLabel->setText('TopLevel window closed');
144+
$topLevel->destroy();
145+
}
146+
]);
147+
$closeBtn->pack(['pady' => 10]);
148+
}
149+
]);
150+
$topLevelButton->pack(['pady' => 10]);
151+
152+
// Color Picker Dialog
153+
$colorButton = new Button($window->getId(), [
154+
'text' => 'Choose Color',
155+
'command' => function () use ($dynamicLabel) {
156+
$color = TopLevel::chooseColor();
157+
if ($color) {
158+
$dynamicLabel->setText("Selected color: $color");
159+
$dynamicLabel->setForeground($color);
160+
}
161+
}
162+
]);
163+
$colorButton->pack(['pady' => 5]);
164+
165+
// File Open Dialog
166+
$fileButton = new Button($window->getId(), [
167+
'text' => 'Open File',
168+
'command' => function () use ($dynamicLabel) {
169+
$file = TopLevel::getOpenFile();
170+
if ($file) {
171+
$dynamicLabel->setText("Selected: " . basename($file));
172+
}
173+
}
174+
]);
175+
$fileButton->pack(['pady' => 5]);
176+
177+
// Directory Chooser Dialog
178+
$dirButton = new Button($window->getId(), [
179+
'text' => 'Choose Directory',
180+
'command' => function () use ($dynamicLabel) {
181+
$dir = TopLevel::chooseDirectory();
182+
if ($dir) {
183+
$dynamicLabel->setText("Directory: " . basename($dir));
184+
}
185+
}
186+
]);
187+
$dirButton->pack(['pady' => 5]);
188+
189+
// Message Box
190+
$msgButton = new Button($window->getId(), [
191+
'text' => 'Show Message',
192+
'command' => function () use ($dynamicLabel) {
193+
$result = TopLevel::messageBox("This is a test message", "okcancel");
194+
$dynamicLabel->setText("Message result: $result");
195+
}
196+
]);
197+
$msgButton->pack(['pady' => 5]);
198+
199+
$app->run();
200+
```
201+
202+
## What This Example Demonstrates
203+
204+
- **Window creation** with custom title and dimensions
205+
- **Labels** with styling (colors, fonts, relief)
206+
- **Buttons** with click callbacks
207+
- **Input fields** with Enter key binding
208+
- **Dynamic updates** — changing widget text, colors at runtime
209+
- **Menus** — main menu bar with submenus and nested menus
210+
- **TopLevel windows** — secondary windows with their own widgets
211+
- **Built-in dialogs** — color picker, file open, directory chooser, message box

0 commit comments

Comments
 (0)