Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make mouse position optional #75

Merged
merged 1 commit into from
Nov 15, 2023
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
52 changes: 41 additions & 11 deletions core/src/main/scala/eu/joaocosta/interim/InputState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,22 @@ sealed trait InputState:
.filterNot(Character.isISOControl)
.mkString

/** Clips the mouse position to a rectagle. If the mouse is outside of the region, it will be moved to (Int.MinValue, Int.MinValue) */
/** Clips the mouse position to a rectagle. If the mouse is outside of the region, the position is set to None */
def clip(area: Rect): InputState

object InputState:

/** Creates a new InputState.
*
* @param mousePosition optional mouse (x, y) position, from the top-left
* @param mousePressed whether the mouse is pressed
* @param keyboardInput
* String generated from the keyboard inputs since the last frame. Usually this will be a single character.
* A `\u0008` character is interpreted as a backspace.
*/
def apply(mousePosition: Option[(Int, Int)], mouseDown: Boolean, keyboardInput: String): InputState =
InputState.Current(InputState.MouseInput(mousePosition, mouseDown), keyboardInput)

/** Creates a new InputState.
*
* @param mouseX mouse X position, from the left
Expand All @@ -50,15 +61,36 @@ object InputState:
* A `\u0008` character is interpreted as a backspace.
*/
def apply(mouseX: Int, mouseY: Int, mouseDown: Boolean, keyboardInput: String): InputState =
InputState.Current(InputState.MouseInput(mouseX, mouseY, mouseDown), keyboardInput)
InputState.Current(InputState.MouseInput(Some((mouseX, mouseY)), mouseDown), keyboardInput)

/** Creates a new InputState with an unknown mouse position.
*
* @param mousePressed whether the mouse is pressed
* @param keyboardInput
* String generated from the keyboard inputs since the last frame. Usually this will be a single character.
* A `\u0008` character is interpreted as a backspace.
*/
def apply(mouseDown: Boolean, keyboardInput: String): InputState =
InputState.Current(InputState.MouseInput(None, mouseDown), keyboardInput)

/** Mouse position and button state.
*
* @param x mouse X position, from the left
* @param y mouse Y position, from the top
* @param position mouse position in a (x, y) tuple. None if the mouse is off-screen.
* @param isPressed whether the mouse is pressed
*/
final case class MouseInput(x: Int, y: Int, isPressed: Boolean)
final case class MouseInput(position: Option[(Int, Int)], isPressed: Boolean):
def x = position.map(_._1)
def y = position.map(_._2)

object MouseInput:
/** Mouse position and button state.
*
* @param x mouse position from the left side
* @param y mouse position from the top
* @param isPressed whether the mouse is pressed
*/
def apply(x: Int, y: Int, isPressed: Boolean): MouseInput =
MouseInput(position = Some((x, y)), isPressed = isPressed)

/** Input state at the current point in time
*
Expand All @@ -71,7 +103,7 @@ object InputState:

def clip(area: Rect): InputState.Current =
if (area.isMouseOver(using this)) this
else this.copy(mouseInput = mouseInput.copy(x = Int.MinValue, y = Int.MinValue))
else this.copy(mouseInput = mouseInput.copy(position = None))

/** Input state at the current point in time and in the previous frame
*
Expand All @@ -95,14 +127,12 @@ object InputState:

/** How much the mouse moved in the X axis */
lazy val deltaX: Int =
if (previousMouseInput.x == Int.MinValue || mouseInput.x == Int.MinValue) 0
else mouseInput.x - previousMouseInput.x
mouseInput.x.zip(previousMouseInput.x).fold(0)((curr, prev) => curr - prev)

/** How much the mouse moved in the Y axis */
lazy val deltaY: Int =
if (previousMouseInput.y == Int.MinValue || mouseInput.y == Int.MinValue) 0
else mouseInput.y - previousMouseInput.y
mouseInput.y.zip(previousMouseInput.y).fold(0)((curr, prev) => curr - prev)

def clip(area: Rect): InputState.Historical =
if (area.isMouseOver(using this)) this
else this.copy(mouseInput = mouseInput.copy(x = Int.MinValue, y = Int.MinValue))
else this.copy(mouseInput = mouseInput.copy(position = None))
4 changes: 3 additions & 1 deletion core/src/main/scala/eu/joaocosta/interim/Rect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ final case class Rect(x: Int, y: Int, w: Int, h: Int):
/** Checks if the mouse is over this area.
*/
def isMouseOver(using inputState: InputState): Boolean =
!(inputState.mouseInput.x < x || inputState.mouseInput.y < y || inputState.mouseInput.x >= x + w || inputState.mouseInput.y >= y + h)
inputState.mouseInput.position.exists((mouseX, mouseY) =>
!(mouseX < x || mouseY < y || mouseX >= x + w || mouseY >= y + h)
)

/** Translates the area to another position.
*/
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/eu/joaocosta/interim/UiContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ final class UiContext private (
val history = InputState.Historical(
previousMouseInput = previousInputState
.map(_.mouseInput)
.getOrElse(InputState.MouseInput(Int.MinValue, Int.MinValue, false)),
.getOrElse(InputState.MouseInput(None, false)),
mouseInput = inputState.mouseInput,
keyboardInput = inputState.keyboardInput
)
Expand Down
17 changes: 9 additions & 8 deletions core/src/main/scala/eu/joaocosta/interim/api/Components.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,15 @@ trait Components:
val clampedValue = math.max(min, math.min(value.get, max))
skin.renderSlider(area, min, clampedValue, max, itemStatus)
if (itemStatus.active)
if (area.w > area.h)
val mousePos = summon[InputState].mouseInput.x - sliderArea.x - sliderSize / 2
val maxPos = sliderArea.w - sliderSize
value := math.max(min, math.min(min + (mousePos * range) / maxPos, max))
else
val mousePos = summon[InputState].mouseInput.y - sliderArea.y - sliderSize / 2
val maxPos = sliderArea.h - sliderSize
value := math.max(min, math.min((mousePos * range) / maxPos, max))
summon[InputState].mouseInput.position.foreach: (mouseX, mouseY) =>
if (area.w > area.h)
val mousePos = mouseX - sliderArea.x - sliderSize / 2
val maxPos = sliderArea.w - sliderSize
value := math.max(min, math.min(min + (mousePos * range) / maxPos, max))
else
val mousePos = mouseY - sliderArea.y - sliderSize / 2
val maxPos = sliderArea.h - sliderSize
value := math.max(min, math.min((mousePos * range) / maxPos, max))

/** Text input component. Returns the current string inputed.
*/
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/eu/joaocosta/interim/api/Layouts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,6 @@ trait Layouts:
final def mouseArea[T](area: Rect)(body: Option[InputState.MouseInput] => T)(using inputState: InputState): T =
body(
Option.when(area.isMouseOver)(
inputState.mouseInput.copy(x = inputState.mouseInput.x - area.x, y = inputState.mouseInput.y - area.y)
inputState.mouseInput.copy(position = inputState.mouseInput.position.map((x, y) => (x - area.x, y - area.y)))
)
)
4 changes: 2 additions & 2 deletions core/src/test/scala/eu/joaocosta/interim/UiContextSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,6 @@ class UiContextSpec extends munit.FunSuite:
val inputState2 = uiContext.pushInputState(InputState(6, 7, false, ""))
assertEquals(inputState2.deltaX, 1)
assertEquals(inputState2.deltaY, 2)
val inputState3 = uiContext.pushInputState(InputState(Int.MinValue, 6, false, ""))
val inputState3 = uiContext.pushInputState(InputState(false, ""))
assertEquals(inputState3.deltaX, 0)
assertEquals(inputState3.deltaY, -1)
assertEquals(inputState3.deltaY, 0)
3 changes: 1 addition & 2 deletions examples/snapshot/example-minart-backend.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ object MinartBackend:
.mkString

private def getInputState(canvas: Canvas): InputState = InputState(
canvas.getPointerInput().position.map(_.x).getOrElse(Int.MinValue),
canvas.getPointerInput().position.map(_.y).getOrElse(Int.MinValue),
canvas.getPointerInput().position.map(pos => (pos.x, pos.y)),
canvas.getPointerInput().isPressed,
processKeyboard(canvas.getKeyboardInput())
)
Expand Down
Loading