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

Add ComponentWithBody abstraction #90

Merged
merged 1 commit into from
Nov 25, 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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ trait Components:
case x: T => applyValue(x)
case x: Ref[T] => applyRef(x)

trait ComponentWithBody[I, F[_]]:
def render[T](body: I => T): Component[F[T]]

def apply[T](body: I => T): Component[F[T]] = render(body)

def apply[T](body: => T)(using ev: I =:= Unit): Component[F[T]] = render(_ => body)

/** Button component. Returns true if it's being clicked, false otherwise.
*
* @param label text label to show on this button
Expand All @@ -36,11 +43,13 @@ trait Components:
area: Rect,
label: String,
skin: ButtonSkin = ButtonSkin.default()
): Component[Boolean] =
val buttonArea = skin.buttonArea(area)
val itemStatus = UiContext.registerItem(id, buttonArea)
skin.renderButton(area, label, itemStatus)
itemStatus.clicked
): ComponentWithBody[Unit, Option] =
new ComponentWithBody[Unit, Option]:
def render[T](body: Unit => T): Component[Option[T]] =
val buttonArea = skin.buttonArea(area)
val itemStatus = UiContext.registerItem(id, buttonArea)
skin.renderButton(area, label, itemStatus)
Option.when(itemStatus.clicked)(body(()))

/** Checkbox component. Returns true if it's enabled, false otherwise.
*/
Expand Down
76 changes: 38 additions & 38 deletions core/shared/src/main/scala/eu/joaocosta/interim/api/Panels.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ trait Panels:
* @param movable if true, the window will include a move handle in the title bar
* @param resizable if true, the window will include a resize handle in the bottom corner
*/
final def window[T](
final def window(
id: ItemId,
area: Rect | PanelState[Rect] | Ref[PanelState[Rect]],
title: String,
Expand All @@ -38,40 +38,40 @@ trait Panels:
resizable: Boolean = false,
skin: WindowSkin = WindowSkin.default(),
handleSkin: HandleSkin = HandleSkin.default()
)(
body: Rect => T
): Components.Component[(Option[T], PanelState[Rect])] =
val panelStateRef = area match
case ref: Ref[PanelState[Rect]] => ref
case v: PanelState[Rect] => Ref(v)
case v: Rect => Ref(PanelState.open(v))
if (panelStateRef.get.isOpen)
def windowArea = panelStateRef.get.value
UiContext.registerItem(id, windowArea, passive = true)
skin.renderWindow(windowArea, title)
val res = body(skin.panelArea(windowArea))
if (closable)
Components
.closeHandle(
id |> "internal_close_handle",
skin.titleTextArea(windowArea),
handleSkin
)(panelStateRef)
if (resizable)
val newArea = Components
.resizeHandle(
id |> "internal_resize_handle",
skin.resizeArea(windowArea),
handleSkin
)(windowArea)
panelStateRef.modify(_.copy(value = skin.ensureMinimumArea(newArea)))
if (movable)
val newArea = Components
.moveHandle(
id |> "internal_move_handle",
skin.titleTextArea(windowArea),
handleSkin
)(windowArea)
panelStateRef.modify(_.copy(value = newArea))
(Option.when(panelStateRef.get.isOpen)(res), panelStateRef.get)
else (None, panelStateRef.get)
): Components.ComponentWithBody[Rect, [T] =>> (Option[T], PanelState[Rect])] =
new Components.ComponentWithBody[Rect, [T] =>> (Option[T], PanelState[Rect])]:
def render[T](body: Rect => T): Components.Component[(Option[T], PanelState[Rect])] =
val panelStateRef = area match
case ref: Ref[PanelState[Rect]] => ref
case v: PanelState[Rect] => Ref(v)
case v: Rect => Ref(PanelState.open(v))
if (panelStateRef.get.isOpen)
def windowArea = panelStateRef.get.value
UiContext.registerItem(id, windowArea, passive = true)
skin.renderWindow(windowArea, title)
val res = body(skin.panelArea(windowArea))
if (closable)
Components
.closeHandle(
id |> "internal_close_handle",
skin.titleTextArea(windowArea),
handleSkin
)(panelStateRef)
if (resizable)
val newArea = Components
.resizeHandle(
id |> "internal_resize_handle",
skin.resizeArea(windowArea),
handleSkin
)(windowArea)
panelStateRef.modify(_.copy(value = skin.ensureMinimumArea(newArea)))
if (movable)
val newArea = Components
.moveHandle(
id |> "internal_move_handle",
skin.titleTextArea(windowArea),
handleSkin
)(windowArea)
panelStateRef.modify(_.copy(value = newArea))
(Option.when(panelStateRef.get.isOpen)(res), panelStateRef.get)
else (None, panelStateRef.get)
12 changes: 6 additions & 6 deletions examples/snapshot/1-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Now, let's write our interface. We are going to need the following components:
def application(inputState: InputState) =
import eu.joaocosta.interim.InterIm.*
ui(inputState, uiContext):
if (button(id = "minus", area = Rect(x = 10, y = 10, w = 30, h = 30), label = "-"))
button(id = "minus", area = Rect(x = 10, y = 10, w = 30, h = 30), label = "-"):
counter = counter - 1
text(
area = Rect(x = 40, y = 10, w = 30, h = 30),
Expand All @@ -64,7 +64,7 @@ def application(inputState: InputState) =
horizontalAlignment = centerHorizontally,
verticalAlignment = centerVertically
)
if (button(id = "plus", area = Rect(x = 70, y = 10, w = 30, h = 30), label = "+"))
button(id = "plus", area = Rect(x = 70, y = 10, w = 30, h = 30), label = "+"):
counter = counter + 1
```

Expand Down Expand Up @@ -128,7 +128,7 @@ def immutableApp(inputState: InputState, counter: Int): (List[RenderOp], Int) =
import eu.joaocosta.interim.InterIm.*
ui(inputState, uiContext):
val (decrementCounter, _, incrementCounter) = (
button(id = "minus", area = Rect(x = 10, y = 10, w = 30, h = 30), label = "-"),
button(id = "minus", area = Rect(x = 10, y = 10, w = 30, h = 30), label = "-")(true).getOrElse(false),
text(
area = Rect(x = 40, y = 10, w = 30, h = 30),
color = Color(0, 0, 0),
Expand All @@ -137,7 +137,7 @@ def immutableApp(inputState: InputState, counter: Int): (List[RenderOp], Int) =
horizontalAlignment = centerHorizontally,
verticalAlignment = centerVertically
),
button(id = "plus", area = Rect(x = 70, y = 10, w = 30, h = 30), label = "+")
button(id = "plus", area = Rect(x = 70, y = 10, w = 30, h = 30), label = "+")(true).getOrElse(false)
)
if (decrementCounter && !incrementCounter) counter - 1
else if (!decrementCounter && incrementCounter) counter + 1
Expand All @@ -154,7 +154,7 @@ def localMutableApp(inputState: InputState, counter: Int): (List[RenderOp], Int)
import eu.joaocosta.interim.InterIm.*
var _counter = counter
ui(inputState, uiContext):
if (button(id = "minus", area = Rect(x = 10, y = 10, w = 30, h = 30), label = "-"))
button(id = "minus", area = Rect(x = 10, y = 10, w = 30, h = 30), label = "-"):
_counter = counter - 1
text(
area = Rect(x = 40, y = 10, w = 30, h = 30),
Expand All @@ -164,7 +164,7 @@ def localMutableApp(inputState: InputState, counter: Int): (List[RenderOp], Int)
horizontalAlignment = centerHorizontally,
verticalAlignment = centerVertically
)
if (button(id = "plus", area = Rect(x = 70, y = 10, w = 30, h = 30), label = "+"))
button(id = "plus", area = Rect(x = 70, y = 10, w = 30, h = 30), label = "+"):
_counter = counter + 1
_counter
```
Expand Down
14 changes: 6 additions & 8 deletions examples/snapshot/2-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ var counter = 0
def application(inputState: InputState) =
import eu.joaocosta.interim.InterIm.*
ui(inputState, uiContext):
columns(area = Rect(x = 10, y = 10, w = 110, h = 30), numColumns = 3, padding = 10) { column =>
if (button(id = "minus", area = column(0), label = "-"))
columns(area = Rect(x = 10, y = 10, w = 110, h = 30), numColumns = 3, padding = 10): column =>
button(id = "minus", area = column(0), label = "-"):
counter = counter - 1
text(
area = column(1),
Expand All @@ -61,9 +61,8 @@ def application(inputState: InputState) =
horizontalAlignment = centerHorizontally,
verticalAlignment = centerVertically
)
if (button(id = "plus", area = column(2), label = "+"))
button(id = "plus", area = column(2), label = "+"):
counter = counter + 1
}
```

Now let's run it:
Expand All @@ -87,10 +86,10 @@ For example, this is how our application would look like with a dynamic layout:
def dynamicApp(inputState: InputState) =
import eu.joaocosta.interim.InterIm.*
ui(inputState, uiContext):
dynamicColumns(area = Rect(x = 10, y = 10, w = 110, h = 30), padding = 10) { column =>
if (button(id = "minus", area = column(30), label = "-")) // 30px from the left
dynamicColumns(area = Rect(x = 10, y = 10, w = 110, h = 30), padding = 10): column =>
button(id = "minus", area = column(30), label = "-"): // 30px from the left
counter = counter - 1
if (button(id = "plus", area = column(-30), label = "+")) // 30px from the right
button(id = "plus", area = column(-30), label = "+"): // 30px from the right
counter = counter + 1
text(
area = column(maxSize), // Fill the remaining area
Expand All @@ -100,5 +99,4 @@ def dynamicApp(inputState: InputState) =
horizontalAlignment = centerHorizontally,
verticalAlignment = centerVertically
)
}
```
4 changes: 2 additions & 2 deletions examples/snapshot/3-windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def application(inputState: InputState) =
ui(inputState, uiContext):
windowArea = window(id = "window", area = windowArea, title = "My Counter", movable = true, closable = false) { area =>
columns(area = area.shrink(5), numColumns = 3, padding = 10) { column =>
if (button(id = "minus", area = column(0), label = "-"))
button(id = "minus", area = column(0), label = "-"):
counter = counter - 1
text(
area = column(1),
Expand All @@ -52,7 +52,7 @@ def application(inputState: InputState) =
horizontalAlignment = centerHorizontally,
verticalAlignment = centerVertically
)
if (button(id = "plus", area = column(2), label = "+"))
button(id = "plus", area = column(2), label = "+"):
counter = counter + 1
}
}._2 // We don't care about the value, just the rect
Expand Down
23 changes: 9 additions & 14 deletions examples/snapshot/4-refs.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ def application(inputState: InputState) =
import eu.joaocosta.interim.InterIm.*
ui(inputState, uiContext):
// window takes area as a ref, so will mutate the window area variable
window(id = "window", area = windowArea, title = "My Counter", movable = true) { area =>
columns(area = area.shrink(5), numColumns = 3, padding = 10) { column =>
if (button(id = "minus", area = column(0), label = "-"))
window(id = "window", area = windowArea, title = "My Counter", movable = true):area =>
columns(area = area.shrink(5), numColumns = 3, padding = 10): column =>
button(id = "minus", area = column(0), label = "-"):
counter = counter - 1
text(
area = column(1),
Expand All @@ -51,10 +51,8 @@ def application(inputState: InputState) =
horizontalAlignment = centerHorizontally,
verticalAlignment = centerVertically
)
if (button(id = "plus", area = column(2), label = "+"))
button(id = "plus", area = column(2), label = "+"):
counter = counter + 1
}
}
```

Be aware that, while the code is more concise, coding with out parameters can lead to confusing code where it's hard
Expand Down Expand Up @@ -82,10 +80,10 @@ val initialState = AppState()
def applicationRef(inputState: InputState, appState: AppState) =
import eu.joaocosta.interim.InterIm.*
ui(inputState, uiContext):
appState.asRefs { (counter, windowArea) =>
window(id = "window", area = windowArea, title = "My Counter", movable = true) { area =>
columns(area = area.shrink(5), numColumns = 3, padding = 10) { column =>
if (button(id = "minus", area = column(0), label = "-"))
appState.asRefs: (counter, windowArea) =>
window(id = "window", area = windowArea, title = "My Counter", movable = true): area =>
columns(area = area.shrink(5), numColumns = 3, padding = 10): column =>
button(id = "minus", area = column(0), label = "-"):
counter := counter.get - 1 // Counter is a Ref, so we need to use :=
text(
area = column(1),
Expand All @@ -95,11 +93,8 @@ def applicationRef(inputState: InputState, appState: AppState) =
horizontalAlignment = centerHorizontally,
verticalAlignment = centerVertically
)
if (button(id = "plus", area = column(2), label = "+"))
button(id = "plus", area = column(2), label = "+"):
counter := counter.get + 1 // Counter is a Ref, so we need to use :=
}
}
}
```

Then we can run our app:
Expand Down
10 changes: 5 additions & 5 deletions examples/snapshot/5-colorpicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@ def application(inputState: InputState, appState: AppState) =
val clipArea = newColumn(maxSize)
clip(area = clipArea):
rows(area = clipArea.copy(y = clipArea.y - resultDelta.get, h = resultsHeight), numRows = results.size, padding = 10): rows =>
results.zip(rows).foreach { case ((colorName, colorValue), row) =>
if (button(s"$colorName button", row, colorName))
colorPickerArea.modify(_.open)
color := colorValue
}
results.zip(rows).foreach:
case ((colorName, colorValue), row) =>
button(s"$colorName button", row, colorName):
colorPickerArea.modify(_.open)
color := colorValue

onBottom:
window(id = "settings", area = PanelState.open(Rect(10, 430, 250, 40)), title = "Settings", movable = false): area =>
Expand Down
Loading