-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
50e38bb
commit a0fee0a
Showing
3 changed files
with
242 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
226 changes: 226 additions & 0 deletions
226
ui/src/main/scala/roguelikestarterkit/ui/components/HitArea.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
package roguelikestarterkit.ui.components | ||
|
||
import indigo.* | ||
import indigo.syntax.* | ||
import roguelikestarterkit.ui.component.Component | ||
import roguelikestarterkit.ui.datatypes.Bounds | ||
import roguelikestarterkit.ui.datatypes.Dimensions | ||
import roguelikestarterkit.ui.datatypes.UIContext | ||
|
||
/** HitAreas `Component` s allow you to create invisible buttons for your UI. | ||
* | ||
* Functionally, a hit area is identical to a button that does not render anything. In fact a | ||
* HitArea is isomorphic to a Button that renders nothing, and its component instance is mostly | ||
* implemented by delegating to the button instance. | ||
* | ||
* All that said... for debug purposes, you can set a fill or stroke color to see the hit area. | ||
*/ | ||
final case class HitArea[ReferenceData]( | ||
bounds: Bounds, | ||
state: ButtonState, | ||
click: ReferenceData => Batch[GlobalEvent], | ||
press: ReferenceData => Batch[GlobalEvent], | ||
release: ReferenceData => Batch[GlobalEvent], | ||
drag: (ReferenceData, DragData) => Batch[GlobalEvent], | ||
boundsType: BoundsType[ReferenceData, Unit], | ||
isDown: Boolean, | ||
dragOptions: DragOptions, | ||
dragStart: Option[DragData], | ||
fill: Option[RGBA] = None, | ||
stroke: Option[Stroke] = None | ||
): | ||
val isDragged: Boolean = dragStart.isDefined | ||
|
||
def onClick(events: ReferenceData => Batch[GlobalEvent]): HitArea[ReferenceData] = | ||
this.copy(click = events) | ||
def onClick(events: Batch[GlobalEvent]): HitArea[ReferenceData] = | ||
onClick(_ => events) | ||
def onClick(events: GlobalEvent*): HitArea[ReferenceData] = | ||
onClick(Batch.fromSeq(events)) | ||
|
||
def onPress(events: ReferenceData => Batch[GlobalEvent]): HitArea[ReferenceData] = | ||
this.copy(press = events) | ||
def onPress(events: Batch[GlobalEvent]): HitArea[ReferenceData] = | ||
onPress(_ => events) | ||
def onPress(events: GlobalEvent*): HitArea[ReferenceData] = | ||
onPress(Batch.fromSeq(events)) | ||
|
||
def onRelease(events: ReferenceData => Batch[GlobalEvent]): HitArea[ReferenceData] = | ||
this.copy(release = events) | ||
def onRelease(events: Batch[GlobalEvent]): HitArea[ReferenceData] = | ||
onRelease(_ => events) | ||
def onRelease(events: GlobalEvent*): HitArea[ReferenceData] = | ||
onRelease(Batch.fromSeq(events)) | ||
|
||
def onDrag( | ||
events: (ReferenceData, DragData) => Batch[GlobalEvent] | ||
): HitArea[ReferenceData] = | ||
this.copy(drag = events) | ||
def onDrag(events: Batch[GlobalEvent]): HitArea[ReferenceData] = | ||
onDrag((_, _) => events) | ||
def onDrag(events: GlobalEvent*): HitArea[ReferenceData] = | ||
onDrag(Batch.fromSeq(events)) | ||
|
||
def withDragOptions(value: DragOptions): HitArea[ReferenceData] = | ||
this.copy(dragOptions = value) | ||
def makeDraggable: HitArea[ReferenceData] = | ||
withDragOptions(dragOptions.withMode(DragMode.Drag)) | ||
def reportDrag: HitArea[ReferenceData] = | ||
withDragOptions(dragOptions.withMode(DragMode.ReportDrag)) | ||
def notDraggable: HitArea[ReferenceData] = | ||
withDragOptions(dragOptions.withMode(DragMode.None)) | ||
|
||
def withDragConstrain(value: DragConstrain): HitArea[ReferenceData] = | ||
this.copy(dragOptions = dragOptions.withConstraints(value)) | ||
def constrainDragTo(bounds: Bounds): HitArea[ReferenceData] = | ||
withDragConstrain(DragConstrain.To(bounds)) | ||
def constrainDragVertically: HitArea[ReferenceData] = | ||
withDragConstrain(DragConstrain.Vertical) | ||
def constrainDragVertically(from: Int, to: Int, x: Int): HitArea[ReferenceData] = | ||
withDragConstrain(DragConstrain.vertical(from, to, x)) | ||
def constrainDragHorizontally: HitArea[ReferenceData] = | ||
withDragConstrain(DragConstrain.Horizontal) | ||
def constrainDragHorizontally(from: Int, to: Int, y: Int): HitArea[ReferenceData] = | ||
withDragConstrain(DragConstrain.horizontal(from, to, y)) | ||
|
||
def withDragArea(value: DragArea): HitArea[ReferenceData] = | ||
this.copy(dragOptions = dragOptions.withArea(value)) | ||
def noDragArea: HitArea[ReferenceData] = | ||
withDragArea(DragArea.None) | ||
def fixedDragArea(bounds: Bounds): HitArea[ReferenceData] = | ||
withDragArea(DragArea.Fixed(bounds)) | ||
def inheritDragArea: HitArea[ReferenceData] = | ||
withDragArea(DragArea.Inherit) | ||
|
||
def withBoundsType(value: BoundsType[ReferenceData, Unit]): HitArea[ReferenceData] = | ||
this.copy(boundsType = value) | ||
|
||
def withFill(value: RGBA): HitArea[ReferenceData] = | ||
this.copy(fill = Option(value)) | ||
def clearFill: HitArea[ReferenceData] = | ||
this.copy(fill = None) | ||
|
||
def withStroke(value: Stroke): HitArea[ReferenceData] = | ||
this.copy(stroke = Option(value)) | ||
def clearStroke: HitArea[ReferenceData] = | ||
this.copy(stroke = None) | ||
|
||
def toButton: Button[ReferenceData] = | ||
Button( | ||
bounds, | ||
state, | ||
(_, _, _) => Outcome(Layer.empty), | ||
None, | ||
None, | ||
click, | ||
press, | ||
release, | ||
drag, | ||
boundsType, | ||
isDown, | ||
dragOptions, | ||
dragStart | ||
) | ||
|
||
object HitArea: | ||
|
||
/** Minimal hitarea constructor with no events. | ||
*/ | ||
def apply[ReferenceData](boundsType: BoundsType[ReferenceData, Unit]): HitArea[ReferenceData] = | ||
HitArea( | ||
Bounds.zero, | ||
ButtonState.Up, | ||
_ => Batch.empty, | ||
_ => Batch.empty, | ||
_ => Batch.empty, | ||
(_, _) => Batch.empty, | ||
boundsType, | ||
isDown = false, | ||
dragOptions = DragOptions.default, | ||
dragStart = None, | ||
fill = None, | ||
stroke = None | ||
) | ||
|
||
/** Minimal hitarea constructor with no events. | ||
*/ | ||
def apply[ReferenceData](bounds: Bounds): HitArea[ReferenceData] = | ||
HitArea( | ||
bounds, | ||
ButtonState.Up, | ||
_ => Batch.empty, | ||
_ => Batch.empty, | ||
_ => Batch.empty, | ||
(_, _) => Batch.empty, | ||
BoundsType.Fixed(bounds), | ||
isDown = false, | ||
dragOptions = DragOptions.default, | ||
dragStart = None, | ||
fill = None, | ||
stroke = None | ||
) | ||
|
||
given [ReferenceData](using btn: Component[Button[ReferenceData], ReferenceData]): Component[ | ||
HitArea[ReferenceData], | ||
ReferenceData | ||
] with | ||
def bounds(reference: ReferenceData, model: HitArea[ReferenceData]): Bounds = | ||
btn.bounds(reference, model.toButton) | ||
|
||
def updateModel( | ||
context: UIContext[ReferenceData], | ||
model: HitArea[ReferenceData] | ||
): GlobalEvent => Outcome[HitArea[ReferenceData]] = | ||
e => | ||
val f = model.fill | ||
val s = model.stroke | ||
btn.updateModel(context, model.toButton)(e).map(_.toHitArea.copy(fill = f, stroke = s)) | ||
|
||
def present( | ||
context: UIContext[ReferenceData], | ||
model: HitArea[ReferenceData] | ||
): Outcome[Layer] = | ||
(model.fill, model.stroke) match | ||
case (Some(fill), Some(stroke)) => | ||
Outcome( | ||
Layer( | ||
Shape.Box( | ||
model.bounds.unsafeToRectangle.moveTo(context.bounds.coords.unsafeToPoint), | ||
Fill.Color(fill), | ||
stroke | ||
) | ||
) | ||
) | ||
|
||
case (Some(fill), None) => | ||
Outcome( | ||
Layer( | ||
Shape.Box( | ||
model.bounds.unsafeToRectangle.moveTo(context.bounds.coords.unsafeToPoint), | ||
Fill.Color(fill) | ||
) | ||
) | ||
) | ||
|
||
case (None, Some(stroke)) => | ||
Outcome( | ||
Layer( | ||
Shape.Box( | ||
model.bounds.unsafeToRectangle.moveTo(context.bounds.coords.unsafeToPoint), | ||
Fill.None, | ||
stroke | ||
) | ||
) | ||
) | ||
|
||
case (None, None) => | ||
Outcome(Layer.empty) | ||
|
||
def refresh( | ||
reference: ReferenceData, | ||
model: HitArea[ReferenceData], | ||
parentDimensions: Dimensions | ||
): HitArea[ReferenceData] = | ||
val f = model.fill | ||
val s = model.stroke | ||
btn.refresh(reference, model.toButton, parentDimensions).toHitArea.copy(fill = f, stroke = s) |