Skip to content

Commit 2f1392a

Browse files
Docs, api
1 parent 066ba2a commit 2f1392a

14 files changed

+142
-7
lines changed

docs/directory.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
laika.navigationOrder = [
22
index.md
33
concepts.md
4+
gotchas.md
45
]

docs/gotchas.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Common Gotchas
2+
3+
**Swing.IO** has a fundementally different base from **Calico**. While it tries to be similar, some **Calico**
4+
patterns are unsupported.
5+
6+
```scala
7+
textField.withSelf { self =>
8+
(
9+
text <-- txtRef,
10+
onValueChange --> {
11+
_.foreach(_ => self.value.get.flatMap(txtRef.set))
12+
}
13+
)
14+
}
15+
```
16+
This pattern causes an infinite loop that consumes all memory in the app.
17+
18+
## Look And Feel
19+
When setting the Look and Feel of an app, Resources can't be prematurely initialized.
20+
21+
```scala
22+
for {
23+
comp <- resourceComponent
24+
win <- window(
25+
lookAndFeel := ???,
26+
// Don't do this, comp will be styled with the default look and feel
27+
comp
28+
)
29+
} yield win
30+
```
31+
32+
Instead, obtain the resource directly. **Swing.IO** supports composing resources together, and it handles the
33+
lifecycles itself.
34+
35+
```scala
36+
for {
37+
comp = resourceComponent
38+
win <- window(
39+
lookAndFeel := ???,
40+
comp
41+
)
42+
} yield win
43+
```
44+
45+
Look and feel can't be updated, as the components style won't update.
46+
47+
## Circumventing DSL
48+
If you have to access a feature that isn't implemented in the DSL yet, you can use flatTaps.
49+
```scala
50+
checkbox(icon := ???).flatTap(_.iconTextGap.set(20).toResource)
51+
```
52+
## Adding custom components
53+
You can technically make your own component by extending a wrapper class. However, it requires wrapping a peer. WrappedRef is exposed to allow for wrapping.
54+
See source code for examples of wrapping.

swingio/src/main/scala/net/bulbyvr/swing/io/AwtEventDispatchEC.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package net.bulbyvr.swing.io
22

33
import scala.concurrent.ExecutionContext
44
import javax.swing.SwingUtilities
5+
6+
/**
7+
* An ExecutionContext that executes on the AWT Event Dispatch thread
8+
*/
59
object AwtEventDispatchEC extends ExecutionContext {
610
def execute(r: Runnable): Unit = SwingUtilities.invokeLater(r)
711
def reportFailure(t: Throwable): Unit = {

swingio/src/main/scala/net/bulbyvr/swing/io/IOSwingApp.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import cats.effect.kernel.Resource
55
import cats.effect.unsafe.implicits.*
66
import cats.syntax.all.*
77
trait IOSwingApp extends IOApp {
8+
/**
9+
* The MainFrame to be rendered
10+
*/
811
def render: Resource[IO, (wrapper.MainFrame[IO], Deferred[IO, Unit])]
912

1013
def run(args: List[String]) = render.flatMap { case t @ (f, _) => Resource.make(f.open *> t.pure[IO])(_ => IO.unit) }.use { case (_, gate) => gate.get } *> ExitCode.Success.pure[IO]

swingio/src/main/scala/net/bulbyvr/swing/io/wrapper/AbstractButton.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ abstract class AbstractButton[F[_]](dispatcher: Dispatcher[F], topic: Topic[F, e
1313
with WithIcon[F] { outer =>
1414
override lazy val peer: JAbstractButton = new JAbstractButton with SuperMixin {}
1515

16+
/**
17+
* Trigger a click on this button
18+
*/
1619
def click: F[Unit] = F.delay { peer.doClick() }.evalOn(AwtEventDispatchEC)
20+
/**
21+
* Trigger a click with n clicks on this button
22+
* @param n number of clicks
23+
*/
1724
def clickN(n: Int): F[Unit] = F.delay { peer.doClick(n) }.evalOn(AwtEventDispatchEC)
1825

1926
lazy val a: ActionListener = _ => {

swingio/src/main/scala/net/bulbyvr/swing/io/wrapper/Adjustable.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,12 @@ object AdjustTo {
3333
}
3434

3535
trait Adjusted[F[_]: Async] {
36+
/**
37+
* Orientation of a component
38+
*/
3639
def adjustTo: RefSource[F, AdjustTo]
3740
}
3841
trait Adjustable[F[_]: Async] extends Adjusted[F] {
42+
3943
override def adjustTo: Ref[F, AdjustTo]
4044
}

swingio/src/main/scala/net/bulbyvr/swing/io/wrapper/ComboBox.scala

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,33 @@ import java.awt.event.{ActionListener, ActionEvent}
1414

1515
class ComboBox[F[_]: Async, A](dispatcher: Dispatcher[F], topic: Topic[F, event.Event[F]]) extends Component[F](topic, dispatcher) with WithRenderer[F, A] {
1616
override lazy val peer: JComboBox[A] = new JComboBox[A]() with SuperMixin
17-
17+
/**
18+
* Renderer
19+
*
20+
* Uses [[ListView.Renderer]] as java uses ListView under the hood.
21+
*/
1822
def renderer: RefSink[F, ListView.Renderer[F, A]] = WrappedSink(it => peer.setRenderer(it.peer))
1923

24+
/**
25+
* The current selected index
26+
*/
2027
def index: Ref[F, Int] = new WrappedRef(peer.getSelectedIndex, peer.setSelectedIndex)
2128

2229
// i love casting : (
30+
/**
31+
* Current selected item.
32+
* WARNING: Setting will not work if the item isn't in the dropdown
33+
*/
2334
def item: Ref[F, A] = new WrappedRef(() => peer.getSelectedItem.asInstanceOf[A], peer.setSelectedItem)
2435

36+
/**
37+
* Max row count that can be displayed without scrolling
38+
*/
2539
def maxRowCount: Ref[F, Int] = new WrappedRef(peer.getMaximumRowCount, peer.setMaximumRowCount)
2640

41+
/**
42+
* Items in box. Write only access.
43+
*/
2744
def items: RefSink[F, Seq[A]] = WrappedSink { it => peer.removeAllItems(); it.foreach(peer.addItem) }
2845

2946
private lazy val il: ActionListener = new ActionListener {

swingio/src/main/scala/net/bulbyvr/swing/io/wrapper/Component.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ abstract class Component[F[_]](topic: Topic[F, event.Event[F]], dispatcher: Disp
1616
trait SuperMixin {}
1717
// TODO: Support custom painting
1818
// Doing that requires wrapping Graphics2D which is kinda a lot
19+
/**
20+
* Name of component, for use with e.g. synth
21+
*/
1922
def name: Ref[F, String] =
2023
new WrappedRef(peer.getName, peer.setName)
2124
def xLayoutAlignment: Ref[F, Float] =
@@ -43,10 +46,20 @@ abstract class Component[F[_]](topic: Topic[F, event.Event[F]], dispatcher: Disp
4346

4447
// TODO: Paint
4548
// Requires wrapping Graphics2D
49+
/**
50+
* Requests focus
51+
*/
4652
def requestFocus: F[Unit] =
4753
F.delay { peer.requestFocus() }.evalOn(AwtEventDispatchEC)
54+
/**
55+
* Request focus in the window this component is in
56+
* @returns success
57+
*/
4858
def requestFocusInWindow: F[Boolean] =
4959
F.delay { peer.requestFocusInWindow() }.evalOn(AwtEventDispatchEC)
60+
/**
61+
* Revalidates this tree
62+
*/
5063
def revalidate: F[Unit] =
5164
F.delay { peer.revalidate() }.evalOn(AwtEventDispatchEC)
5265

swingio/src/main/scala/net/bulbyvr/swing/io/wrapper/Container.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ package wrapper
44
import cats.effect.Async
55

66
trait Container[F[_]](using F: Async[F]) extends UIElement[F] {
7+
/**
8+
* Contents of this container. Read only.
9+
*/
710
def contents: F[Seq[Component[F]]]
811
}
912

swingio/src/main/scala/net/bulbyvr/swing/io/wrapper/FileChooser.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,25 @@ import java.io.File
77
import cats.effect.syntax.all.*
88
import cats.syntax.all.*
99
object FileChooser {
10-
private[io] class PartiallyAppliedApply[F[_]: Async] {
11-
private def getFC: F[JFileChooser] =
10+
final class PartiallyAppliedApply[F[_]] private[io] (val dummy: Boolean = true) extends AnyVal {
11+
private def getFC(using Async[F]): F[JFileChooser] =
1212
Async[F].delay { JFileChooser() }.evalOn(AwtEventDispatchEC)
13-
private def showDialog(show: JFileChooser => Int): F[(Int, JFileChooser)] =
13+
private def showDialog(show: JFileChooser => Int)(using Async[F]): F[(Int, JFileChooser)] =
1414
getFC.flatMap { fc =>
1515
Async[F].delay { show(fc) }.evalOn(AwtEventDispatchEC).map { res =>
1616
(res, fc)
1717
}
1818
}
19-
private def showOpenDialog(show: JFileChooser => Int): F[Option[File]] =
19+
private def showOpenDialog(show: JFileChooser => Int)(using Async[F]): F[Option[File]] =
2020
showDialog(show).flatMap { (res, fc) =>
2121
if (res == JFileChooser.APPROVE_OPTION)
2222
Async[F].delay { Option(fc.getSelectedFile) }.evalOn(AwtEventDispatchEC)
2323
else
2424
None.pure[F]
2525

2626
}
27-
def save: F[Option[File]] = showOpenDialog(_.showSaveDialog(null))
28-
def open: F[Option[File]] = showOpenDialog(_.showOpenDialog(null))
27+
def save(using Async[F]): F[Option[File]] = showOpenDialog(_.showSaveDialog(null))
28+
def open(using Async[F]): F[Option[File]] = showOpenDialog(_.showOpenDialog(null))
2929

3030
}
3131
def apply[F[_]: Async]: PartiallyAppliedApply[F] = new PartiallyAppliedApply[F]

0 commit comments

Comments
 (0)