Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 12 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ lazy val e2eTest = (project in file("cyfra-e2e-test"))
.settings(commonSettings, runnerSettings)
.dependsOn(runtime)

lazy val rtrp = (project in file("cyfra-rtrp"))
.settings(commonSettings)
.dependsOn(utility, vulkan)
.settings(
libraryDependencies ++= Seq(
"org.lwjgl" % "lwjgl-glfw" % lwjglVersion,
"org.lwjgl" % "lwjgl-glfw" % lwjglVersion classifier lwjglNatives
)
)

lazy val root = (project in file("."))
.settings(name := "Cyfra")
.aggregate(
Expand All @@ -103,7 +113,8 @@ lazy val root = (project in file("."))
foton,
runtime,
vulkan,
examples
examples,
rtrp
)

e2eTest / Test / javaOptions ++= Seq(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package io.computenode.cyfra.rtrp.window

import io.computenode.cyfra.window.core._
import io.computenode.cyfra.window.platform.GLFWWindowSystem
import scala.util.{Try, Success, Failure}

class WindowManager {
private var windowSystem: Option[WindowSystem] = None
private var eventHandlers: Map[Class[_ <: WindowEvent], WindowEvent => Unit] = Map.empty

def initialize(): Try[Unit] = {
if (windowSystem.isDefined) {
return Failure(new IllegalStateException("WindowManager already initialized"))
}

val glfwSystem = new GLFWWindowSystem()
glfwSystem.initialize().map { _ =>
windowSystem = Some(glfwSystem)
}
}

def shutdown(): Try[Unit] = {
windowSystem match {
case Some(system) =>
val result = system.shutdown()
windowSystem = None
result
case None =>
Success(())
}
}

def createWindow(): Try[Window] = {
createWindow(WindowConfig())
}

def createWindow(config: WindowConfig): Try[Window] = {
windowSystem match {
case Some(system) => system.createWindow(config)
case None => Failure(new IllegalStateException("WindowManager not initialized"))
}
}

def createWindow(configure: WindowConfig => WindowConfig): Try[Window] = {
val config = configure(WindowConfig())
createWindow(config)
}

def pollAndDispatchEvents(): Try[Unit] = {
windowSystem match {
case Some(system) =>
system.pollEvents().map { events =>
events.foreach(dispatchEvent)
}
case None =>
Failure(new IllegalStateException("WindowManager not initialized"))
}
}

def onEvent[T <: WindowEvent](eventClass: Class[T])(handler: T => Unit): Unit = {
eventHandlers = eventHandlers + (eventClass -> handler.asInstanceOf[WindowEvent => Unit])
}

def onWindowClose(handler: WindowEvent.CloseRequested => Unit): Unit = {
onEvent(classOf[WindowEvent.CloseRequested])(handler)
}

def onWindowResize(handler: WindowEvent.Resized => Unit): Unit = {
onEvent(classOf[WindowEvent.Resized])(handler)
}

def onKeyPress(handler: InputEvent.KeyPressed => Unit): Unit = {
onEvent(classOf[InputEvent.KeyPressed])(handler)
}

def onMouseClick(handler: InputEvent.MousePressed => Unit): Unit = {
onEvent(classOf[InputEvent.MousePressed])(handler)
}

def getActiveWindows(): List[Window] = {
windowSystem.map(_.getActiveWindows()).getOrElse(List.empty)
}

def isInitialized: Boolean = windowSystem.isDefined

private def dispatchEvent(event: WindowEvent): Unit = {
eventHandlers.get(event.getClass).foreach(_(event))
}
}

object WindowManager {

def create(): Try[WindowManager] = {
val manager = new WindowManager()
manager.initialize().map(_ => manager)
}

def withManager[T](action: WindowManager => Try[T]): Try[T] = {
create().flatMap { manager =>
try {
action(manager)
} finally {
manager.shutdown()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package io.computenode.cyfra.rtrp.window

import io.computenode.cyfra.window.core._
import scala.util.{Try, Success, Failure}

object WindowSystemExample {

def main(args: Array[String]): Unit = {
println("Starting Window System Example")

val result = WindowManager.withManager { manager =>
runExample(manager)
}

result match {
case Success(_) => println("Example completed successfully")
case Failure(ex) =>
println(s"Example failed: ${ex.getMessage}")
ex.printStackTrace()
}
}

private def runExample(manager: WindowManager): Try[Unit] = Try {
manager.onWindowClose { event =>
println(s"Window ${event.windowId} close requested")
}

manager.onWindowResize { event =>
println(s"Window ${event.windowId} resized to ${event.width}x${event.height}")
}

manager.onKeyPress { event =>
println(s"Key pressed: ${event.key.code} in window ${event.windowId}")
}

manager.onMouseClick { event =>
println(s"Mouse clicked: button ${event.button.code} at (${event.x}, ${event.y}) in window ${event.windowId}")
}

// Create a window
val window = manager.createWindow { config =>
config.copy(
width = 1024,
height = 768,
title = "Window Example",
position = Some(WindowPosition.Centered)
)
}.get

println(s"Created window: ${window.id}")

// Main loop
var running = true
var frameCount = 0

while (running && !window.shouldClose) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have an abstraction over the "game loop", but probably not at this stage. In what part could we put it later on?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe new gameloop or engine-like module

// Poll and handle events
manager.pollAndDispatchEvents()

// Simple frame counter
frameCount += 1
if (frameCount % 60 == 0) {
println(s"Frame $frameCount - Window active: ${window.isVisible}")
}

// Simulate some work (in real, this would be rendering)
Thread.sleep(16) // ~60 FPS

if (frameCount >= 1000) {
running = false
}
}

println("Main loop ended")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.computenode.cyfra.window.core

import scala.util.Try

// Unique identifier for windows
case class WindowId(value: Long) extends AnyVal

// Platform-agnostic window interface
trait Window {
def id: WindowId
def properties: WindowProperties
def nativeHandle: Long // Platform-specific handle

// Window operations
def show(): Try[Unit]
def hide(): Try[Unit]
def close(): Try[Unit]
def focus(): Try[Unit]
def minimize(): Try[Unit]
def maximize(): Try[Unit]
def restore(): Try[Unit]

// Property changes
def setTitle(title: String): Try[Unit]
def setSize(width: Int, height: Int): Try[Unit]
def setPosition(x: Int, y: Int): Try[Unit]

// Queries
def shouldClose: Boolean
def isVisible: Boolean
def isFocused: Boolean
def isMinimized: Boolean
def isMaximized: Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.computenode.cyfra.window.core

case class WindowConfig(
width: Int = 800,
height: Int = 600,
title: String = "Cyfra Window",
resizable: Boolean = true,
decorated: Boolean = true,
fullscreen: Boolean = false,
vsync: Boolean = true,
samples: Int = 1, // MSAA samples
position: Option[WindowPosition] = None
)

sealed trait WindowPosition
object WindowPosition {
case object Centered extends WindowPosition
case class Fixed(x: Int, y: Int) extends WindowPosition
case object Default extends WindowPosition
}

case class WindowProperties(
width: Int,
height: Int,
title: String,
visible: Boolean,
focused: Boolean,
minimized: Boolean,
maximized: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.computenode.cyfra.window.core

// Base trait for all window events
sealed trait WindowEvent {
def windowId: WindowId
}


// Window lifecycle events
object WindowEvent {
case class Created(windowId: WindowId) extends WindowEvent
case class Destroyed(windowId: WindowId) extends WindowEvent
case class CloseRequested(windowId: WindowId) extends WindowEvent
case class Resized(windowId: WindowId, width: Int, height: Int) extends WindowEvent
case class Moved(windowId: WindowId, x: Int, y: Int) extends WindowEvent
case class FocusChanged(windowId: WindowId, focused: Boolean) extends WindowEvent
case class VisibilityChanged(windowId: WindowId, visible: Boolean) extends WindowEvent
case class Minimized(windowId: WindowId) extends WindowEvent
case class Maximized(windowId: WindowId) extends WindowEvent
case class Restored(windowId: WindowId) extends WindowEvent
}


// Input events
sealed trait InputEvent extends WindowEvent

object InputEvent {
case class KeyPressed(windowId: WindowId, key: Key, modifiers: KeyModifiers) extends InputEvent
case class KeyReleased(windowId: WindowId, key: Key, modifiers: KeyModifiers) extends InputEvent
case class KeyRepeated(windowId: WindowId, key: Key, modifiers: KeyModifiers) extends InputEvent
case class CharacterInput(windowId: WindowId, codepoint: Int) extends InputEvent

case class MousePressed(windowId: WindowId, button: MouseButton, x: Double, y: Double, modifiers: KeyModifiers) extends InputEvent
case class MouseReleased(windowId: WindowId, button: MouseButton, x: Double, y: Double, modifiers: KeyModifiers) extends InputEvent
case class MouseMoved(windowId: WindowId, x: Double, y: Double) extends InputEvent
case class MouseScrolled(windowId: WindowId, xOffset: Double, yOffset: Double) extends InputEvent
case class MouseEntered(windowId: WindowId) extends InputEvent
case class MouseExited(windowId: WindowId) extends InputEvent
}

case class Key(code: Int)
case class MouseButton(code: Int)

case class KeyModifiers(
shift: Boolean = false,
ctrl: Boolean = false,
alt: Boolean = false,
`super`: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.computenode.cyfra.window.core

import scala.util.Try

// Main interface for window system operations
trait WindowSystem {

def initialize(): Try[Unit]
def shutdown(): Try[Unit]
def createWindow(config: WindowConfig): Try[Window]
def destroyWindow(window: Window): Try[Unit]
def pollEvents(): Try[List[WindowEvent]]
def getActiveWindows(): List[Window]
def findWindow(id: WindowId): Option[Window]
def isInitialized: Boolean
}
Loading