All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.joaocosta.interim.api.Components.scala Maven / Gradle / Ivy

package eu.joaocosta.interim.api

import eu.joaocosta.interim.*
import eu.joaocosta.interim.skins.*

/** Object containing the default components.
  *
  * By convention, all components are functions in the form `def component(id, area, ...params, skin)(value): Value`.
  */
object Components extends Components

trait Components:

  type Component[+T] = (inputState: InputState, uiState: UiState) ?=> T

  trait ComponentWithValue[T] {
    def apply(value: Ref[T]): Component[T]
    def apply(value: T): Component[T] =
      apply(Ref(value))
    inline def applyUnion(value: T | Ref[T]): Component[T] = inline value match
      case x: T      => apply(x)
      case x: Ref[T] => apply(x)
  }

  /** Button component. Returns true if it's being clicked, false otherwise.
    *
    * @param label text label to show on this button
    */
  final def button(
      id: ItemId,
      area: Rect,
      label: String,
      skin: ButtonSkin = ButtonSkin.default()
  ): Component[Boolean] =
    val buttonArea = skin.buttonArea(area)
    val itemStatus = UiState.registerItem(id, buttonArea)
    skin.renderButton(area, label, itemStatus)
    itemStatus.hot && itemStatus.active && summon[InputState].mouseDown == false

  /** Checkbox component. Returns true if it's enabled, false otherwise.
    */
  final def checkbox(id: ItemId, area: Rect, skin: CheckboxSkin = CheckboxSkin.default()): ComponentWithValue[Boolean] =
    new ComponentWithValue[Boolean]:
      def apply(value: Ref[Boolean]): Component[Boolean] =
        val checkboxArea = skin.checkboxArea(area)
        val itemStatus   = UiState.registerItem(id, checkboxArea)
        skin.renderCheckbox(area, Ref.get[Boolean](value), itemStatus)
        if (itemStatus.hot && itemStatus.active && summon[InputState].mouseDown == false)
          Ref.modify[Boolean](value, v => !v)
        else Ref.get(value)

  /** Radio button component. Returns value currently selected.
    *
    * @param buttonValue the value of this button (value that this button returns when selected)
    * @param label text label to show on this button
    */
  final def radioButton[T](
      id: ItemId,
      area: Rect,
      buttonValue: T,
      label: String,
      skin: ButtonSkin = ButtonSkin.default()
  ): ComponentWithValue[T] =
    new ComponentWithValue[T]:
      def apply(value: Ref[T]): Component[T] =
        val buttonArea = skin.buttonArea(area)
        val itemStatus = UiState.registerItem(id, buttonArea)
        val newValue =
          if (itemStatus.hot && itemStatus.active && summon[InputState].mouseDown == false)
            Ref.set[T](value, buttonValue)
          else Ref.get[T](value)
        if (newValue == buttonValue) skin.renderButton(area, label, itemStatus.copy(hot = true, active = true))
        else (skin.renderButton(area, label, itemStatus))
        newValue

  /** Slider component. Returns the current position of the slider, between min and max.
    *
    * @param min minimum value for this slider
    * @param max maximum value fr this slider
    */
  final def slider(
      id: ItemId,
      area: Rect,
      min: Int,
      max: Int,
      skin: SliderSkin = SliderSkin.default()
  ): ComponentWithValue[Int] =
    new ComponentWithValue[Int]:
      def apply(value: Ref[Int]): Component[Int] =
        val sliderArea   = skin.sliderArea(area)
        val sliderSize   = skin.sliderSize(area, min, max)
        val range        = max - min
        val itemStatus   = UiState.registerItem(id, sliderArea)
        val clampedValue = math.max(min, math.min(Ref.get[Int](value), max))
        skin.renderSlider(area, min, clampedValue, max, itemStatus)
        if (itemStatus.active)
          if (area.w > area.h)
            val mousePos = summon[InputState].mouseX - sliderArea.x - sliderSize / 2
            val maxPos   = sliderArea.w - sliderSize
            Ref.set(value, math.max(min, math.min(min + (mousePos * range) / maxPos, max)))
          else
            val mousePos = summon[InputState].mouseY - sliderArea.y - sliderSize / 2
            val maxPos   = sliderArea.h - sliderSize
            Ref.set(value, math.max(min, math.min((mousePos * range) / maxPos, max)))
        else Ref.get(value)

  /** Text input component. Returns the current string inputed.
    */
  final def textInput(
      id: ItemId,
      area: Rect,
      skin: TextInputSkin = TextInputSkin.default()
  ): ComponentWithValue[String] =
    new ComponentWithValue[String]:
      def apply(value: Ref[String]): Component[String] =
        val textInputArea = skin.textInputArea(area)
        val itemStatus    = UiState.registerItem(id, textInputArea)
        skin.renderTextInput(area, Ref.get(value), itemStatus)
        if (itemStatus.keyboardFocus) Ref.modify(value, summon[InputState].appendKeyboardInput)
        else Ref.get(value)

  /** Draggable handle. Returns the moved area.
    *
    * It's important that this element moves along with the moved area.
    *
    * Instead of using this component directly, it can be easier to use [[eu.joaocosta.interim.api.Panels.window]]
    * with movable = true.
    */
  final def moveHandle(id: ItemId, area: Rect, skin: HandleSkin = HandleSkin.default()): ComponentWithValue[Rect] =
    new ComponentWithValue[Rect]:
      def apply(value: Ref[Rect]): Component[Rect] =
        val handleArea = skin.handleArea(area)
        val itemStatus = UiState.registerItem(id, handleArea)
        skin.renderHandle(area, Ref.get(value), itemStatus)
        if (itemStatus.active)
          val handleCenterX = handleArea.x + handleArea.w / 2
          val handleCenterY = handleArea.y + handleArea.h / 2
          val mouseX        = summon[InputState].mouseX
          val mouseY        = summon[InputState].mouseY
          val deltaX        = mouseX - handleCenterX
          val deltaY        = mouseY - handleCenterY
          Ref.modify(value, _.move(deltaX, deltaY))
        else Ref.get(value)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy