From 37c2a1db67dec28f23198b52a1e11a2dae1b129b Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Sat, 15 Nov 2025 19:07:20 +0100 Subject: [PATCH] Backup --- examples/kanban_app.php | 79 ++++++++++++++++++++++++++++++++++++++- examples/kanban_data.json | 17 +++++---- src/Ui/Component.php | 13 +++++++ src/Ui/Window.php | 29 +++++++++++--- 4 files changed, 123 insertions(+), 15 deletions(-) diff --git a/examples/kanban_app.php b/examples/kanban_app.php index e3ceefa..82036d2 100644 --- a/examples/kanban_app.php +++ b/examples/kanban_app.php @@ -37,7 +37,8 @@ final class KanbanTaskCard extends Container return false; } - $this->boardView->selectTask($this->boardId, $this->task); + // Start drag operation on click + $this->boardView->beginDrag($this->boardId, $this->task, $this); return true; } } @@ -90,6 +91,7 @@ final class KanbanBoardView extends Container private array $boardViews = []; private null|string $selectedTaskId = null; private null|string $selectedBoardId = null; + private null|array $dragState = null; public function __construct( private readonly string $storagePath, @@ -158,6 +160,41 @@ final class KanbanBoardView extends Container $this->renderBoards(); } + public function beginDrag(string $boardId, array $task, KanbanTaskCard $card): void + { + $vp = $card->getViewport(); + + $ghost = new Container('bg-yellow-300 border-2 border-red-600 rounded px-3 py-2 shadow-2xl'); + $ghost->addComponent(new Label($task['title'], 'text-sm text-black')); + $ghost->setViewport(clone $vp); + $ghost->setContentViewport(clone $vp); + $ghost->setOverlay(true); + + $this->addComponent($ghost); + + $this->dragState = [ + 'fromBoardId' => $boardId, + 'task' => $task, + 'ghost' => $ghost, + ]; + + $this->statusLabel->setText('Drag gestartet: ' . $task['title']); + } + + public function handleDrag(float $mouseX, float $mouseY): void + { + if ($this->dragState === null) { + return; + } + /** @var Container $ghost */ + $ghost = $this->dragState['ghost']; + $vp = $ghost->getViewport(); + $vp->x = (int) ($mouseX - $vp->width / 2); + $vp->y = (int) ($mouseY - $vp->height / 2); + $ghost->setViewport($vp); + $ghost->setContentViewport(clone $vp); + } + public function moveSelectedToBoard(string $targetBoardId): void { if ($this->selectedTaskId === null) { @@ -218,6 +255,46 @@ final class KanbanBoardView extends Container } } + public function handleMouseRelease(float $mouseX, float $mouseY, int $button): void + { + parent::handleMouseRelease($mouseX, $mouseY, $button); + + if ($button !== 1 || $this->dragState === null) { + return; + } + + // Determine drop target board + $targetBoardId = null; + foreach ($this->boardViews as $boardId => $info) { + $vp = $info['column']->getViewport(); + if ( + $mouseX >= $vp->x && + $mouseX <= ($vp->x + $vp->width) && + $mouseY >= $vp->y && + $mouseY <= ($vp->y + $vp->height) + ) { + $targetBoardId = $boardId; + break; + } + } + + // Remove ghost + if (($this->dragState['ghost'] ?? null) instanceof Container) { + $this->dragState['ghost']->setVisible(false); + } + + $state = $this->dragState; + $this->dragState = null; + + if ($targetBoardId !== null) { + $this->selectedBoardId = $state['fromBoardId']; + $this->selectedTaskId = $state['task']['id']; + $this->moveSelectedToBoard($targetBoardId); + } else { + $this->statusLabel->setText('Drag abgebrochen.'); + } + } + private function renderBoards(): void { $this->clearChildren(); diff --git a/examples/kanban_data.json b/examples/kanban_data.json index 739dcd0..f4608a1 100644 --- a/examples/kanban_data.json +++ b/examples/kanban_data.json @@ -7,26 +7,27 @@ "id": "task_691661d89de735.41071535", "title": "Idee sammeln", "note": "" - }, - { - "id": "task_691661d89de8e0.86926319", - "title": "UI Grundlayout", - "note": "" } ] }, { "id": "board_691661d89de806.79123800", "title": "In Arbeit", - "tasks": [] + "tasks": [ + { + "id": "task_691661d89de858.46479539", + "title": "API anbinden", + "note": "" + } + ] }, { "id": "board_691661d89de894.20053237", "title": "Erledigt", "tasks": [ { - "id": "task_691661d89de858.46479539", - "title": "API anbinden", + "id": "task_691661d89de8e0.86926319", + "title": "UI Grundlayout", "note": "" } ] diff --git a/src/Ui/Component.php b/src/Ui/Component.php index 0a9e5b6..6e53cb4 100644 --- a/src/Ui/Component.php +++ b/src/Ui/Component.php @@ -344,6 +344,19 @@ abstract class Component } } + /** + * Optional: handle drag (mouse move with pressed button). + * Default: propagate to children. + */ + public function handleDrag(float $mouseX, float $mouseY): void + { + foreach ($this->children as $child) { + if (method_exists($child, 'handleDrag')) { + $child->handleDrag($mouseX, $mouseY); + } + } + } + public function layout(null|TextRenderer $textRenderer = null): void { $this->normalStylesCached = StyleParser::parse($this->style)->getValidStyles( diff --git a/src/Ui/Window.php b/src/Ui/Window.php index 080110f..bd9253e 100644 --- a/src/Ui/Window.php +++ b/src/Ui/Window.php @@ -17,7 +17,7 @@ class Window private Viewport $viewport; private bool $shouldBeReLayouted = true; private float $pixelRatio = 1.0; - private float $uiScale = 2.0; + private float $uiScale = 1.0; private bool $shouldClose = false; private $onResize = null; private $onFpsChange = null; @@ -51,15 +51,19 @@ class Window $flags = SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_RESIZABLE; $this->window = sdl_create_window($title, $width, $height, $flags); - if (!$this->window) { throw new \Exception('Failed to create window: ' . sdl_get_error()); } // Get window ID for event routing $this->windowId = sdl_get_window_id($this->window); - - $this->uiScale = sdl_get_window_display_scale($this->window); + // Use display scale as UI scale (e.g. 2.0 on HiDPI) + if (function_exists('sdl_get_window_display_scale')) { + $scale = sdl_get_window_display_scale($this->window); + if ($scale > 0.1 && $scale <= 4.0) { + $this->uiScale = (float) $scale; + } + } // Enable text input for this window sdl_start_text_input($this->window); @@ -237,6 +241,10 @@ class Window case SDL_EVENT_QUIT: case SDL_EVENT_WINDOW_CLOSE_REQUESTED: $this->shouldClose = true; + // Drag handling: mouse move while left button is pressed + if ($this->leftButtonDown && $this->rootComponent) { + $this->rootComponent->handleDrag($this->mouseX, $this->mouseY); + } break; case SDL_EVENT_KEY_DOWN: @@ -283,8 +291,9 @@ class Window break; case SDL_EVENT_MOUSE_MOTION: - $newMouseX = (float) ($event['x'] ?? 0); - $newMouseY = (float) ($event['y'] ?? 0); + // Convert physical pixels to logical coordinates using uiScale + $newMouseX = (float) ($event['x'] ?? 0) / $this->uiScale; + $newMouseY = (float) ($event['y'] ?? 0) / $this->uiScale; $this->mouseX = $newMouseX; $this->mouseY = $newMouseY; @@ -316,6 +325,10 @@ class Window case SDL_EVENT_MOUSE_BUTTON_DOWN: $button = $event['button'] ?? 0; + if ($button === 1) { + $this->leftButtonDown = true; + } + // Check overlays first (in reverse z-index order - highest first) if ($this->rootComponent) { $overlays = $this->rootComponent->collectOverlays(); @@ -342,6 +355,10 @@ class Window case SDL_EVENT_MOUSE_BUTTON_UP: $button = $event['button'] ?? 0; + if ($button === 1) { + $this->leftButtonDown = false; + } + // Propagate release to root component if ($this->rootComponent) { $this->rootComponent->handleMouseRelease($this->mouseX, $this->mouseY, $button);