Game flow and coloring done

This commit is contained in:
Savya Bikram Shah
2026-05-28 19:45:15 +05:45
parent 9ac752b23c
commit b3c096a150
48 changed files with 850 additions and 138 deletions

151
Readme.md
View File

@@ -630,7 +630,7 @@ public readonly struct PaperSavedSignal {
- Regions are pre-authored as children of the drawing prefab (under `PaperRoot`). Each is a `ColorRegionView` (UI `Image` + `IPointerClickHandler`). On `Awake`, the view sets `Image.alphaHitTestMinimumThreshold` from a serialized field (default `0.01f`; tune up to `0.5f` for tighter hits) so taps on transparent pixels pass through to the next region below.
- **`ColorPaletteHolderView`** owns the palette container `RectTransform` + slide-in/out animation (same pattern as `ShapeHolderView``Tween.UIAnchoredPosition` + `Tween.Alpha` on `CanvasGroup`, hides off-screen right).
- **`ColorPaletteHolderPresenter`** subscribes to `ShapeAssembledSignal``view.Show()`. Hide is invoked from scene tear-down or future phase-exit hook.
- **`ColorPaletteView` + `ColorPalettePresenter`** (planned): view spawns one color button per `IColorPalette.Colors` entry under the holder's `SpawnRoot`; presenter wires button click → `ColoringStateRepository.CurrentColor`.
- **`ColorButton` + `IColorButtonFactory`** (planned): each color is its own self-contained MB (swatch `Image` + `IPointerClickHandler`). `ColorButtonFactory` instantiates the button prefab under `ColorPaletteHolderView.SpawnRoot` and calls `button.Setup(color, state, sfx)`. On click, the button writes directly to `ColoringStateRepository.SelectColor(myColor)` — no view/presenter intermediary. `ColoringController` iterates `IColorPalette.Colors` once on init and calls `factory.Create(color)` per entry. Mirrors `ShapePieceFactory` for symmetry.
- Each region's `OnPointerClick``ColoringController.PaintRegion(view)`.
- Controller builds `PaintRegionCommand(regionId, oldColor, newColor)` and pushes to `IUndoStack`. Command sets `Image.color` on Execute/Undo.
- Publishes `ColorAppliedSignal` for SFX / sparkle effects.
@@ -1107,7 +1107,7 @@ public sealed class ColoringModule : MonoBehaviour, IServiceModule {
builder.Register<ColoringController>(Lifetime.Scoped)
.As<IColoringController>()
.AsSelf();
builder.Register<ColorPalettePresenter>(Lifetime.Scoped).AsSelf();
builder.Register<IColorButtonFactory, ColorButtonFactory>(Lifetime.Scoped);
builder.RegisterEntryPoint<ColoringInputBinder>();
}
}
@@ -1215,75 +1215,98 @@ public sealed class BoundedUndoStack : IUndoStack {
---
## 25. View / Presenter Pair — Color Palette
## 25. Self-contained Button + Factory — Color Palette
### View (MonoBehaviour, setters only)
The palette deliberately **skips the View/Presenter pair**. Each color is its own self-contained `ColorButton` MB, spawned by a factory that mirrors `ShapePieceFactory`. The button writes directly to `ColoringStateRepository` on click — no intermediary indexed event, no presenter wiring `_state.SelectedIndexChanged → _view.SetSelected`.
Why: palette behavior is per-button (each one is a discrete tap target with its own selected/hover anim), and adding per-button variants later (locked colors, magic "rainbow" button, premium colors) is just a new MB type that the factory can branch to. A View+Presenter pair would force every variant through a single `SetColors(IReadOnlyList<Color>)` setter.
### Button (self-contained MB)
```csharp
namespace Darkmatter.Features.Coloring.UI;
public sealed class ColorPaletteView : MonoBehaviour, IColorPaletteView {
[SerializeField, RequireInterface(typeof(IColorButtonView))]
private MonoBehaviour[] _buttonsRaw;
public sealed class ColorButton : MonoBehaviour, IPointerClickHandler {
[SerializeField] private Image swatch;
[SerializeField] private GameObject selectedRing; // optional highlight
private IColorButtonView[] _buttons;
private Color _color;
private ColoringStateRepository _state;
private ISfxPlayer _sfx;
public event Action<int> OnColorButtonClicked;
public Color Color => _color;
private void Awake() {
_buttons = _buttonsRaw.Cast<IColorButtonView>().ToArray();
for (var i = 0; i < _buttons.Length; i++) {
var idx = i;
_buttons[i].OnClicked += () => OnColorButtonClicked?.Invoke(idx);
}
}
public void SetColors(IReadOnlyList<Color> colors) {
for (var i = 0; i < _buttons.Length; i++)
_buttons[i].SetVisible(i < colors.Count);
for (var i = 0; i < colors.Count; i++)
_buttons[i].SetColor(colors[i]);
}
public void SetSelected(int index) {
for (var i = 0; i < _buttons.Length; i++)
_buttons[i].SetSelected(i == index);
}
}
```
### Presenter (pure C#)
```csharp
namespace Darkmatter.Features.Coloring.UI;
public sealed class ColorPalettePresenter : IStartable, IDisposable {
private readonly IColorPaletteView _view;
private readonly ColoringStateRepository _state;
public ColorPalettePresenter(IColorPaletteView view, ColoringStateRepository state) {
_view = view;
public void Setup(Color color, ColoringStateRepository state, ISfxPlayer sfx) {
_color = color;
_state = state;
_sfx = sfx;
swatch.color = color;
_state.SelectedColorChanged += OnSelectedChanged;
OnSelectedChanged(_state.CurrentColor);
}
public void Start() {
_view.SetColors(_state.Palette.Colors);
_view.SetSelected(_state.SelectedIndex);
_view.OnColorButtonClicked += OnClicked;
_state.SelectedIndexChanged += OnIndexChanged;
public void OnPointerClick(PointerEventData _) {
_sfx.Play(SfxId.UiTap);
_state.SelectColor(_color);
}
private void OnClicked(int index) => _state.SelectColor(index);
private void OnIndexChanged(int index) => _view.SetSelected(index);
private void OnSelectedChanged(Color current) {
if (selectedRing != null) selectedRing.SetActive(current == _color);
}
public void Dispose() {
_view.OnColorButtonClicked -= OnClicked;
_state.SelectedIndexChanged -= OnIndexChanged;
private void OnDestroy() {
if (_state != null) _state.SelectedColorChanged -= OnSelectedChanged;
}
}
```
Same shape repeats for every feature's UI.
### Factory (mirrors ShapePieceFactory)
```csharp
namespace Darkmatter.Features.Coloring.Systems;
public interface IColorButtonFactory {
ColorButton Create(Color color);
}
public sealed class ColorButtonFactory : IColorButtonFactory {
private readonly ColorPaletteHolderView _holder;
private readonly GameObject _buttonPrefab;
private readonly ColoringStateRepository _state;
private readonly ISfxPlayer _sfx;
public ColorButtonFactory(
ColorPaletteHolderView holder,
GameObject buttonPrefab,
ColoringStateRepository state,
ISfxPlayer sfx) {
_holder = holder;
_buttonPrefab = buttonPrefab;
_state = state;
_sfx = sfx;
}
public ColorButton Create(Color color) {
var go = Object.Instantiate(_buttonPrefab, _holder.SpawnRoot);
var btn = go.GetComponent<ColorButton>();
btn.Setup(color, _state, _sfx);
return btn;
}
}
```
### Spawn loop (inside `ColoringController.InitializeAsync`)
```csharp
foreach (var color in _palette.Colors)
_buttonFactory.Create(color);
```
That's the whole feature wiring. No `IColorPaletteView`, no `OnColorButtonClicked` indexed event, no `SelectedIndex ↔ SetSelected` round-trip.
### When to use View/Presenter instead
Other features (history buttons, drawing catalog) still use View/Presenter — that pair fits when a single canvas of fixed sub-widgets needs to react to one model. Use button+factory when each item is **spawned dynamically from data** and has independent click behavior.
---
@@ -1592,7 +1615,8 @@ Comprehensive index — every script (existing or planned) grouped by its module
| `Features/ShapeBuilder/Systems/` | `ShapeBuilderController`, `IShapePieceFactory`, `ShapePieceFactory` | ✅ |
| `Features/ShapeBuilder/Installers/` | `ShapeBuilderFeatureModule` | ✅ |
| `Features/Coloring/Systems/` | `ColoringController`, `ColoringStateRepository` | ⚠️ planned |
| `Features/Coloring/UI/` | `ColorRegionView` ✅, `ColorPaletteHolderView` ✅, `ColorPaletteHolderPresenter` ✅, `ColorPaletteView` ⚠️, `ColorPalettePresenter` ⚠️ | |
| `Features/Coloring/UI/` | `ColorRegionView` ✅, `ColorPaletteHolderView` ✅, `ColorPaletteHolderPresenter` ✅, `ColorButton` ⚠️ planned | |
| `Features/Coloring/Systems/` | `IColorButtonFactory`, `ColorButtonFactory` | ⚠️ planned |
| `Features/Coloring/Commands/` | `PaintRegionCommand` | ⚠️ |
| `Features/Coloring/Installers/` | `ColoringFeatureModule` | ⚠️ |
| `Features/Capture/Systems/` | `CaptureController` (light wrapper around `ICaptureService`) | ⚠️ |
@@ -2093,14 +2117,14 @@ In-memory model. Owns "currently selected color" and the palette in use.
```csharp
public sealed class ColoringStateRepository {
public IColorPalette Palette { get; private set; }
public int SelectedIndex { get; private set; }
public Color CurrentColor => Palette.Colors[SelectedIndex];
public event Action<int> SelectedIndexChanged;
public void SetPalette(IColorPalette palette); // resets SelectedIndex to 0
public void SelectColor(int index);
public Color CurrentColor { get; private set; }
public event Action<Color> SelectedColorChanged;
public void SetPalette(IColorPalette palette); // resets CurrentColor to Colors[0]
public void SelectColor(Color color); // called directly by ColorButton on click
}
```
- **Why a repository:** presenter and controller both need to read/write current color; an event-emitting POCO is simpler than wiring two signals.
- **Why a repository:** `ColorButton` (palette taps) and `ColoringController` (paint command builder) both need to read/write the current color; an event-emitting POCO is simpler than wiring two signals.
- **Color-keyed, not index-keyed:** with `ColorButton + Factory` (§25), each button owns its `Color` directly. Index-based addressing is unnecessary — the button passes its color straight to `SelectColor(color)`.
#### `ColoringController` *(Systems — planned)* — implements `IColoringController`
Wires pre-authored `ColorRegionView` children, applies saved colors on resume, builds and pushes `PaintRegionCommand` instances on click.
@@ -2156,8 +2180,8 @@ No `ColoringInputBinder` class needed. Unity's EventSystem fires `OnPointerClick
#### `PaintRegionCommand` *(Commands)*
Source in section 23. Holds `view`, `fromColor`, `toColor`, `bus`. Symmetrical execute/undo.
#### `ColorPaletteView`, `ColorPalettePresenter` *(UI — stubs exist)*
Files exist but bodies are empty. Target shape: view spawns one color button per `IColorPalette.Colors` entry under `ColorPaletteHolderView.SpawnRoot`; presenter binds `ColoringStateRepository.SelectedIndexChanged` ↔ view highlight, and view click events → repository.
#### `ColorButton` + `IColorButtonFactory` / `ColorButtonFactory` *(UI + Systems — planned)*
Replaces the View/Presenter pair for the palette. Each color is its own self-contained MB with click handler; factory instantiates the prefab under `ColorPaletteHolderView.SpawnRoot` and runs `Setup(color, state, sfx)`. Button writes `_state.SelectColor(_color)` on click. See §25 for the full pattern and rationale. The stale `ColorPaletteView.cs` / `ColorPalettePresenter.cs` stubs should be deleted when this lands.
#### `ColorPaletteHolderView` / `ColorPaletteHolderPresenter` *(UI — ✅ exists)*
View owns the palette container `RectTransform` (`SpawnRoot`) + slide/fade show-hide animation (same `Tween.UIAnchoredPosition` + `Tween.Alpha` pattern as `ShapeHolderView`, default hide off-screen right). Presenter subscribes `ShapeAssembledSignal``view.Show()`. Hide is invoked externally (scope tear-down or future phase-exit signal).
@@ -2165,6 +2189,7 @@ View owns the palette container `RectTransform` (`SpawnRoot`) + slide/fade show-
#### Removed / not needed
- **`ColorRegionFactory`** — regions are pre-authored as children of the drawing prefab. The controller wires existing views; it doesn't spawn anything.
- **`ColorPaletteView` + `ColorPalettePresenter`** — replaced by self-contained `ColorButton` + `ColorButtonFactory`. The stub files at `Features/Coloring/UI/ColorPaletteView.cs` and `ColorPalettePresenter.cs` should be deleted; see §25 for rationale.
---
@@ -2356,6 +2381,8 @@ Implemented as `MonoBehaviour` per feature/service so scopes can drag them in th
| `ColoringStateRepository` | Feature.Coloring | Current color model | — |
| `ColoringController` | Feature.Coloring | Wires pre-authored regions + paint cmd + autosave hook | undo, state, refs, flow, bus |
| `ColorPaletteHolderView` / `ColorPaletteHolderPresenter` | Feature.Coloring | Palette container + slide/fade show/hide; presenter reacts to `ShapeAssembledSignal` | bus |
| `ColorButton` | Feature.Coloring | Self-contained palette swatch MB; click writes to repository (no view/presenter pair) | state, sfx |
| `IColorButtonFactory` / `ColorButtonFactory` | Feature.Coloring | Instantiates a `ColorButton` under `ColorPaletteHolderView.SpawnRoot` + runs `Setup` | holder, prefab, state, sfx |
| `ColorRegionView` | Feature.Coloring | Region UI Image + `IPointerClickHandler` | controller |
| `PaintRegionCommand` | Feature.Coloring | Undoable paint (sets `Image.color`) | view, bus |
| `HistoryController` | Feature.History | Undo/redo facade | undo stack, bus |