Skip to content

slideshow_example.kt

src/jvmMain/kotlin/afield/examples/slideshow_example.kt · 651 lines

@file:OptIn(DelicateCoroutinesApi::class)

package afield.examples

import afield.document.graph.Box
import afield.document.graph.autorun
import afield.app.diagnostics.logging.lprintln
import afield.ui.slides.markdownNote
import afield.app.notifications.notify
import afield.ui.slides.slideShow
import afield.app.commands.action
import afield.app.commands.actions
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

Example slideshows demonstrating the slideshow DSL

To use these, create boxes in Field and run the code in one of them. The slideshow will guide users through the document.

NEW FEATURES: - Custom link callbacks: Links can execute Kotlin code via #custom:callbackName - Template system: Use {{propertyName}} to reference box properties/variables - Shared chat: Use withChat(id = "templateId") to share chat across template instances - Custom chat IDs: Create shared discussion spaces with custom IDs

See interactiveGuideExample() for a comprehensive demonstration of these features.

fun Box.simpleInteractiveExample()

Simple example demonstrating custom link callbacks and templates

This is a minimal example showing the core new features: - Link callbacks that execute custom code - Template system for live data display

fun Box.simpleInteractiveExample() {
    // Variable that will be displayed and modified
    var count = 0
    this.properties["count"] = count

    val show = slideShow {
        step("Interactive Example") {
            markdownNote(
                """
                ### Simple Interactive Guide (Markdown)
                Current count: **{{count}}**

                [Increment](#custom:increment)
                """.trimIndent()
            )

            linkCallback("increment") {
                count++
                here.properties["count"] = count
                // Refresh the step to show new count
                show.goToStep(show.currentStepIndex)
            }

            selectBox(this@simpleInteractiveExample)
        }
    }

    GlobalScope.launch {
        show.start()
    }
}

Example slideshow demonstrating the slideshow DSL

To use this, create boxes in Field and run this code in one of them. The slideshow will guide users through the document.

fun Box.exampleSlideShow()

body — 124 lines, from line 78
fun Box.exampleSlideShow() {
    // Get references to boxes (you can also use selectBoxByName)
    val welcomeBox = this
    val codeBox = this.children.firstOrNull() ?: this
    val visualBox = this.children.iterator().next()
    this.autorun = true

    // Create the slideshow
    val show = slideShow {
        step("Welcome to Field") {
            markdownNote(
                """
                ## Welcome to Field!
                This is an interactive guided tour of Field, a live coding environment.

                Field combines high-performance graphics with powerful scripting capabilities.

                **Click the links below to navigate:**

                [[start:Start the tour →]] · [[home:🏠 Home]]
                """.trimIndent(), animated = true
            )

            selectBox(welcomeBox, property = "code")
        }

        step("Understanding Boxes") {
            markdownNote(
                """
                ### Boxes are the Core Abstraction
                In Field, everything is a **Box**. Boxes can:

                - Contain code in multiple languages (Kotlin, Python, JavaScript, Clojure)
                - Have properties and metadata
                - Connect to other boxes to form execution graphs
                - Execute code and produce output

                [[back]] [[next]]
                """.trimIndent()
            )

            // Example: Select box by name instead of by reference
            // selectBoxByName("My Code Box", property = "code", line = 1)
            selectBox(codeBox, property = "code", line = 1)
        }

        step("Writing Code") {
            markdownNote(
                """
                ### Live Coding
                You can write code directly in boxes and see the results immediately.

                The code editor supports:

                - Syntax highlighting
                - Code completion
                - AI-powered assistance

                Try editing the code and pressing `Cmd+Enter` (Mac) or `Ctrl+Enter` (Windows/Linux) to execute!

                [[back]] [[next]]
                """.trimIndent()
            )

            selectBox(codeBox, property = "code", line = 5, highlight = 5)

            onEnter {
                // You could run some code when entering this step
                lprintln("info,slideshow", "User entered the 'Writing Code' step")
            }
        }

        step("Visual Output") {
            markdownNote(
                """
                ### Graphics and Visualization
                Field excels at real-time graphics and visualization.

                You can create:

                - 2D and 3D graphics
                - Shaders (GLSL)
                - Interactive visualizations
                - Data plots

                [[back]] [[next]]
                """.trimIndent(),
                anchorBox = visualBox
            )

            selectBox(visualBox, property = "fragment")
        }

        step("Next Steps") {
            markdownNote(
                """
                ### Keep Exploring!
                You've completed the basic tour. Here are some things to try:

                - Create new boxes by double-clicking on the canvas
                - Connect boxes by dragging from one to another
                - Explore the example templates
                - Ask the AI assistant for help

                **Happy coding!**

                [[back]] [[finish:Finish tour]] [[home]]
                """.trimIndent(),
                persistent = true
            )

            selectBox(welcomeBox, property = "code")

            onEnter {
                lprintln("info,slideshow", "User completed the tour!")
            }
        }
    }

    // Start the slideshow
    GlobalScope.launch {
        show.start()
    }
}

fun Box.simpleSlideShowExample()

Simple example showing basic API usage

fun Box.simpleSlideShowExample() {
    val show = slideShow {
        step("Step 1") {
            markdownNote(
                """
                ### First Step
                Welcome!

                [[next]]
                """.trimIndent()
            )
            selectBox(this@simpleSlideShowExample)
        }

        step("Step 2") {
            markdownNote(
                """
                ### Second Step
                Look at line 10!

                [[back]]
                """.trimIndent()
            )
            selectBox(this@simpleSlideShowExample, line = 10, highlight = 10)
        }
    }

    GlobalScope.launch {
        show.start()
    }
}

fun Box.slideShowWithImagesExample()

Example showing how to use images in slideshow notes

To use this: 1. Place an image (e.g., "example.png") in your project directory 2. Create a box and run this code 3. The slideshow will display the image in a note

body — 55 lines, from line 246
fun Box.slideShowWithImagesExample() {
    val show = slideShow {
        step("Welcome") {
            markdownNote(
                """
                ## Slideshow with Images
                This example demonstrates how to include images in your slideshows.

                Images are served from your project directory (workspace).

                [[next:Continue →]]
                """.trimIndent()
            )
            selectBox(this@slideShowWithImagesExample)
        }

        step("Image Example") {
            markdownNote(
                """
                ### Including Images
                Place images in your project directory and reference them with `/project/`:

                ![Example image](/project/example.png)

                _Note: If the image doesn't appear, make sure "example.png" exists in your project directory._

                [[back]] [[next]]
                """.trimIndent()
            )
            selectBox(this@slideShowWithImagesExample)
        }

        step("Subdirectories") {
            markdownNote(
                """
                ### Organizing Images
                You can organize images in subdirectories:

                - `/project/images/diagram.png`
                - `/project/screenshots/ui.jpg`
                - `/project/docs/architecture.svg`

                Always use forward slashes, even on Windows.

                [[back]] [[finish]]
                """.trimIndent()
            )
            selectBox(this@slideShowWithImagesExample)
        }
    }

    GlobalScope.launch {
        show.start()
    }
}

fun Box.installSlideShowControls()

Helper to install slideshow controls as actions on a box

fun Box.installSlideShowControls() {
    // Add actions to control the slideshow
    this.actions["▶ Start Slideshow"] =
        action(desc = "▶ Start Slideshow", detail = "Start the slideshow associated with this box") {
            GlobalScope.launch {
                this@installSlideShowControls.slideShow?.start()
            }
        }

    this.actions["→ Next Step"] = action(desc = "→ Next Step", detail = "Advance slide show forwards") {
        GlobalScope.launch {
            this@installSlideShowControls.slideShow?.next()
        }
    }

    this.actions["← Previous Step"] = action(desc = "← Previous Step", detail = "Go back one slide") {
        GlobalScope.launch {
            this@installSlideShowControls.slideShow?.previous()
        }
    }

    this.actions["■ Stop Slideshow"] = action("■ Stop Slideshow", detail = "Hide the slideshow") {
        GlobalScope.launch {
            this@installSlideShowControls.slideShow?.stop()
        }
    }
}

fun Box.advancedSlideShowExample()

Advanced slideshow example with annotations and animations

body — 105 lines, from line 336
fun Box.advancedSlideShowExample() {
    val hostBox = this
    val codeBox = this.children.firstOrNull() ?: this
    val secondBox = this.children.elementAtOrNull(1) ?: codeBox

    val show = slideShow {
        step("Introduction with Animation") {
            markdownNote(
                """
                ## Welcome!
                This guide demonstrates advanced slideshow features.

                Notice how this text animates in letter by letter!

                [[next:Continue →]]
                """.trimIndent(),
                animated = true
            )

            selectBox(hostBox)
        }

        step("Drawing Attention with Annotations") {
            markdownNote(
                """
                ### Pointing Things Out
                You can draw lines from the guide to boxes!

                <span id="annotation-target">Look at the line connecting this guide to the box below.</span>

                [[back]] [[next]]
                """.trimIndent()
            )

            selectBox(codeBox)

            // Draw an annotation line from the guide to the target box
            annotate(
                id = "main-annotation",
                targetBox = codeBox,
                color = "rgba(128, 255, 164, 0.8)",
                strokeWidth = 2.5
            )
        }

        step("Pointing to Specific Code Lines") {
            markdownNote(
                """
                ### Code-Level Annotations
                Lines can point to specific code locations!

                The annotation below targets line 5 in the editor.

                [[back]] [[next]]
                """.trimIndent(),
                animated = true
            )

            selectBox(codeBox, line = 5, highlight = 5)

            // Draw annotation to a specific line
            annotateToLine(
                id = "line-annotation",
                targetBox = codeBox,
                property = "code",
                line = 5,
                color = "rgba(255, 180, 100, 0.8)",
                strokeWidth = 2.0,
                dashed = true
            )
        }

        step("Multiple Annotations") {
            markdownNote(
                """
                ### Complex Guides
                You can have multiple annotation lines at once.

                This helps explain relationships between different parts of your document.

                [[back]] [[finish]]
                """.trimIndent(),
                persistent = true
            )

            selectBox(hostBox)

            // Multiple annotations
            annotate(
                id = "box1",
                targetBox = codeBox,
                color = "rgba(100, 200, 255, 0.7)"
            )
            annotate(
                id = "box2",
                targetBox = secondBox,
                color = "rgba(255, 100, 200, 0.7)"
            )
        }
    }

    GlobalScope.launch {
        show.start()
    }
}

fun Box.interactiveGuideExample()

Example demonstrating interactive links with callbacks and template system

This example shows: 1. Custom link callbacks defined in code 2. Template system that references box properties and variables

All slides use the Markdown front end, so you can exercise advanced features without writing raw HTML.

body — 199 lines, from line 452
fun Box.interactiveGuideExample() {
    val hostBox = this
    // Set up some properties and variables that can be referenced in templates
    var clickCount = 0
    var userChoice = "none"
    val version = "1.0.0"

    // Store properties on the box for template access
    this.properties["clickCount"] = clickCount
    this.properties["userChoice"] = userChoice
    this.properties["version"] = version

    val show = slideShow {
        slideshowId("__test_0")
        step("Welcome to Interactive Guides") {
            markdownNote(
                """
                ## Interactive Guides with Callbacks
                Written entirely in Markdown (no custom HTML required).

                - **Custom link callbacks** trigger Kotlin code
                - **Template system** shows live data (version **{{version}}**)
                - **Navigation shortcuts** via `[[next]]` and `[[back]]`

                [[next:Let's explore →]]
                """.trimIndent()
            )

            selectBox(hostBox)
        }

        step("Custom Link Callbacks") {
            markdownNote(
                """
                ### Clickable callbacks using Markdown links
                Use Markdown links with `#custom:name` to run Kotlin from the slide.

                - [Increment](#custom:incrementCounter)
                - [Reset](#custom:resetCounter)

                Times clicked: **{{clickCount}}**

                [[back]] [[next]]
                """.trimIndent()
            )

            // Register callbacks for the links
            linkCallback("incrementCounter") {
                clickCount++
                here.properties["clickCount"] = clickCount
                // Refresh the current step to show updated count
                show.goToStep(show.currentStepIndex)
                lprintln("info,slideshow", "Counter incremented to $clickCount")
            }

            linkCallback("resetCounter") {
                clickCount = 0
                here.properties["clickCount"] = clickCount
                // Refresh the current step to show updated count
                show.goToStep(show.currentStepIndex)
                lprintln("info,slideshow", "Counter reset")
            }

            selectBox(hostBox)
        }

        step("Template System") {
            markdownNote(
                """
                ### Live Data Display
                Templates pull values from box properties or variables in scope.

                - Counter value: **{{clickCount}}**
                - Version: **{{version}}**
                - User choice: **{{userChoice}}**

                [[back]] [[next]]
                """.trimIndent()
            )

            selectBox(hostBox)
        }

        step("Live Chat") {
            markdownNote(
                """
                ### Per-slide live chat (Markdown)
                Open the chat pane to ask questions while staying on this slide.

                This chat is specific to this slideshow instance.

                [[back]] [[next]]
                """.trimIndent()
            )
            withChat()
            selectBox(hostBox)
        }

        step("Shared Chat Across Templates") {
            markdownNote(
                """
                ### Shared chat across template instances
                Use `withChat(id = "templateId")` to create a shared discussion space
                across all boxes created from the same template.

                This is useful for:
                - Collaborative learning materials
                - Shared project discussions
                - Multi-user experiences

                [[back]] [[next]]
                """.trimIndent()
            )
            // This chat would be shared across all instances of this template.
            //
            // withChat(id = ...) resolves the chat thread three ways (see the KDoc on
            // SlideStepBuilder.withChat / Box.withChat in SlideShow.kt):
            //   withChat()                 -> instance-specific (slideshow id, or box id
            //                                 for the standalone Box.withChat helper)
            //   withChat(id = "templateId")-> shared across all boxes from the same
            //                                 template (resolves to Box.templateId)
            //   withChat(id = "anything")  -> a custom shared thread across any boxes
            // The standalone Box.withChat(id = ...) mounts the same chat UI into a box's
            // "chat" output channel (outside a slideshow) with the identical id rules.
            withChat(id = "templateId")
            selectBox(hostBox)
        }

        step("Make a Choice") {
            markdownNote(
                """
                ### Interactive Choices
                Pick one option; the callback updates `{{userChoice}}` via Markdown links.

                - [Option A: Performance Mode](#custom:choiceA)
                - [Option B: Quality Mode](#custom:choiceB)
                - [Option C: Balanced Mode](#custom:choiceC)

                Your choice: **{{userChoice}}**

                [[back]] [[next]]
                """.trimIndent()
            )

            linkCallback("choiceA") {
                userChoice = "Performance Mode"
                here.properties["userChoice"] = userChoice
                show.goToStep(show.currentStepIndex)
                notify("Selected: Performance Mode", "slideshow")
            }

            linkCallback("choiceB") {
                userChoice = "Quality Mode"
                here.properties["userChoice"] = userChoice
                show.goToStep(show.currentStepIndex)
                notify("Selected: Quality Mode", "slideshow")
            }

            linkCallback("choiceC") {
                userChoice = "Balanced Mode"
                here.properties["userChoice"] = userChoice
                show.goToStep(show.currentStepIndex)
                notify("Selected: Balanced Mode", "slideshow")
            }

            selectBox(hostBox)
        }

        step("Advanced: Computed Properties") {
            markdownNote(
                """
                ### Computing Display Values
                Values are prepared in `onEnter` and shown via Markdown templates.

                - Click count: {{clickCount}}
                - Doubled: {{doubledCount}}
                - Status: **{{status}}**

                [[back]] [[finish:Finish]]
                """.trimIndent()
            )

            onEnter {
                // Compute derived values when entering this step
                hostBox.properties["doubledCount"] = clickCount * 2
                hostBox.properties["status"] =
                    if (clickCount > 10) "Very Active!"
                    else if (clickCount > 5) "Active"
                    else "Getting Started"
            }

            selectBox(hostBox)
        }
    }

    GlobalScope.launch {
        show.start()
    }
}