Game flow and coloring done
This commit is contained in:
151
Readme.md
151
Readme.md
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user