Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion docs/Editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ When the main panel has focus and the `Scene` tab is active, it uses scene-view
#### Select Mode

Use Select Mode to move between visible scene objects without changing them.
If the selected object has no renderable sprite or text, Scene View shows a muted `x` at its transform position.

Controls:

Expand Down Expand Up @@ -324,6 +325,7 @@ Controls:

- `Up` / `Down`: move between controls
- `Enter`: activate the selected control
- `/`: toggle the focused collapsible section, such as `Transform`, `Renderer`, or a component block

#### 2. Property Selection

Expand Down Expand Up @@ -434,18 +436,28 @@ The Console panel currently reads from:

```text
<project_root>/logs/debug.log
<project_root>/logs/error.log
```

Current behavior:

- on editor startup it loads the last three log lines
- it has two tabs:
- `Debug`: reads from `logs/debug.log` if it exists
- `Error`: reads from `logs/error.log` if it exists
- on editor startup each tab loads the last three lines from its own log file
- log display is clipped to the console viewport
- it only reads a tab's log file if that file exists
- it auto-refreshes the console tabs from disk every `editor.console.refreshInterval` seconds while the editor is in Play Mode
- when the console has focus and the editor is not in play mode, it supports scrolling
- if no refresh interval is configured, the editor uses a default of `5` seconds

Controls:

- `Tab`: switch to the next console tab
- `Shift+Tab`: switch to the previous console tab
- `Up`: scroll up through older log lines
- `Down`: scroll down through newer log lines
- `Shift+R`: manually refresh the active log tab from disk and jump to the newest visible lines

The scroll stops:

Expand All @@ -459,6 +471,18 @@ Current tag colors:
- `[INFO]`: blue
- `[DEBUG]`: light gray

Configuration:

```json
{
"editor": {
"console": {
"refreshInterval": 5
}
}
}
```

## Saving

Press `Ctrl+S` to save the loaded scene.
Expand Down
2 changes: 2 additions & 0 deletions src/Editor/Editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,8 @@ private function initializeWidgets(): void
);
$this->consolePanel = new ConsolePanel(
logFilePath: Path::join($this->workingDirectory, 'logs', 'debug.log'),
errorLogFilePath: Path::join($this->workingDirectory, 'logs', 'error.log'),
refreshIntervalSeconds: $this->settings->consoleRefreshIntervalSeconds,
);
$this->inspectorPanel = new InspectorPanel(
workingDirectory: $this->workingDirectory,
Expand Down
34 changes: 31 additions & 3 deletions src/Editor/EditorSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

class EditorSettings
{
public const float DEFAULT_CONSOLE_REFRESH_INTERVAL_SECONDS = 5.0;

protected(set) int $width {
get {
return $this->width;
Expand All @@ -23,7 +25,8 @@ class EditorSettings
* @param EditorSceneSettings $scenes
*/
public function __construct(
public readonly EditorSceneSettings $scenes
public readonly EditorSceneSettings $scenes,
public readonly float $consoleRefreshIntervalSeconds = self::DEFAULT_CONSOLE_REFRESH_INTERVAL_SECONDS,
)
{
$terminalSize = get_max_terminal_size();
Expand Down Expand Up @@ -61,6 +64,31 @@ public static function loadFromDirectory(string $workingDirectory): self
*/
public static function fromArray(array $data): self
{
return new self(scenes: EditorSceneSettings::fromArray($data["scenes"] ?? []));
$editorData = is_array($data['editor'] ?? null) ? $data['editor'] : $data;
$scenesData = $editorData['scenes'] ?? $data['scenes'] ?? [];
$consoleData = is_array($editorData['console'] ?? null) ? $editorData['console'] : [];
$refreshInterval = $consoleData['refreshInterval']
?? $editorData['consoleRefreshInterval']
?? self::DEFAULT_CONSOLE_REFRESH_INTERVAL_SECONDS;

return new self(
scenes: EditorSceneSettings::fromArray(is_array($scenesData) ? $scenesData : []),
consoleRefreshIntervalSeconds: self::normalizeRefreshInterval($refreshInterval),
);
}

private static function normalizeRefreshInterval(mixed $refreshInterval): float
{
if (!is_numeric($refreshInterval)) {
return self::DEFAULT_CONSOLE_REFRESH_INTERVAL_SECONDS;
}

$normalizedRefreshInterval = (float) $refreshInterval;

if ($normalizedRefreshInterval <= 0) {
return self::DEFAULT_CONSOLE_REFRESH_INTERVAL_SECONDS;
}

return $normalizedRefreshInterval;
}
}
}
1 change: 1 addition & 0 deletions src/Editor/IO/Enumerations/KeyCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum KeyCode: string
case BACKSPACE = 'backspace';
case ESCAPE = 'escape';
case DELETE = 'delete';
case SLASH = '/';
case CTRL_C = 'ctrl_c';
case CTRL_S = 'ctrl_s';
case CTRL_Y = 'ctrl_y';
Expand Down
48 changes: 45 additions & 3 deletions src/Editor/IO/InputManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ class InputManager implements StaticObservableInterface
{
use StaticObservableTrait;

private const array COALESCED_REPEATABLE_KEYS = [
KeyCode::UP->value,
KeyCode::RIGHT->value,
KeyCode::DOWN->value,
KeyCode::LEFT->value,
];

/**
* @var string The current key press.
*/
Expand Down Expand Up @@ -110,10 +117,15 @@ public static function enableEcho(): void
public static function handleInput(): void
{
self::$previousKeyPress = self::$keyPress;
if (self::$inputQueue === []) {
self::$inputQueue = self::tokenizeInput(stream_get_contents(STDIN) ?: '');

$incomingTokens = self::tokenizeInput(stream_get_contents(STDIN) ?: '');

if ($incomingTokens !== []) {
self::$inputQueue = [...self::$inputQueue, ...$incomingTokens];
}

self::$inputQueue = self::coalesceRepeatableTokens(self::$inputQueue);

self::$keyPress = array_shift(self::$inputQueue) ?? '';
self::$mouseEvent = self::parseMouseEvent(self::$keyPress);

Expand Down Expand Up @@ -277,7 +289,7 @@ public static function areAllKeysPressed(array $keyCodes): bool
*/
public static function isKeyPressed(KeyCode $keyCode): bool
{
return self::$keyPress === $keyCode->value;
return self::getKey(self::$keyPress) === $keyCode->value;
}

/**
Expand Down Expand Up @@ -396,6 +408,36 @@ private static function tokenizeInput(string $input): array
return $tokens;
}

private static function coalesceRepeatableTokens(array $tokens): array
{
if ($tokens === []) {
return [];
}

$coalescedTokens = [];
$previousNormalizedToken = null;
$previousWasRepeatable = false;

foreach ($tokens as $token) {
if (!is_string($token) || $token === '') {
continue;
}

$normalizedToken = self::getKey($token);
$isRepeatableToken = in_array($normalizedToken, self::COALESCED_REPEATABLE_KEYS, true);

if ($isRepeatableToken && $previousWasRepeatable && $normalizedToken === $previousNormalizedToken) {
continue;
}

$coalescedTokens[] = $token;
$previousNormalizedToken = $normalizedToken;
$previousWasRepeatable = $isRepeatableToken;
}

return $coalescedTokens;
}

private static function extractEscapeSequence(string $input): string
{
$knownSequences = [
Expand Down
Loading
Loading