Skip to content

Commit

Permalink
Initial renderer for SDL
Browse files Browse the repository at this point in the history
  • Loading branch information
Szymon Lorenz committed Oct 1, 2023
1 parent 65a6ad0 commit cd37f1b
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 0 deletions.
39 changes: 39 additions & 0 deletions Sources/TokamakSDL2/App/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2020-2021 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Created by Szymon on 2/10/23.
//

import OpenCombineShim
import SDL
import TokamakCore

public extension App {
static func _launch(_ app: Self, with configuration: _AppConfiguration) {
_ = Unmanaged.passRetained(SDLRenderer(app, configuration.rootEnvironment))
}

static func _setTitle(_ title: String) {
guard let window = SDLRenderer.shared?.window else { return }
SDL_SetWindowTitle(window, title)
}

var _phasePublisher: AnyPublisher<ScenePhase, Never> {
CurrentValueSubject(.active).eraseToAnyPublisher()
}

var _colorSchemePublisher: AnyPublisher<ColorScheme, Never> {
CurrentValueSubject(.light).eraseToAnyPublisher()
}
}
118 changes: 118 additions & 0 deletions Sources/TokamakSDL2/SDLRenderer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import Dispatch
import Foundation
import SDL
@_spi(TokamakCore) import TokamakCore

extension EnvironmentValues {
/// Returns default settings for the GTK environment
static var defaultEnvironment: Self {
var environment = EnvironmentValues()
environment[_ColorSchemeKey] = .light
// environment._defaultAppStorage = LocalStorage.standard
// _DefaultSceneStorageProvider.default = SessionStorage.standard

return environment
}
}

final class SDLRenderer: Renderer {
static var shared: SDLRenderer?
private(set) var reconciler: StackReconciler<SDLRenderer>?
private(set) var window: OpaquePointer?
private var renderer: OpaquePointer?

init<A: App>(_ app: A, _ environment: EnvironmentValues) {
window = SDL_CreateWindow(
"SDL Tokamak Renderer",
Int32(SDL_WINDOWPOS_CENTERED_MASK),
Int32(SDL_WINDOWPOS_CENTERED_MASK),
800,
600,
UInt32(SDL_WINDOW_SHOWN.rawValue)
)

renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED.rawValue)

if let window {
reconciler = StackReconciler(
app: app,
target: SDLTarget(window: window),
environment: .defaultEnvironment.merging(environment),
renderer: self,
scheduler: { next in
DispatchQueue.main.async {
next()
SDL_ShowWindow(window)
}
}
)
}

SDLRenderer.shared = self
}

func mountTarget(
before sibling: SDLTarget?,
to parent: SDLTarget,
with host: MountedHost
) -> TargetType? {
guard let anyTarget = mapAnyView(
host.view,
transform: { (target: AnySDL) in target }
) else {
if mapAnyView(host.view, transform: { (view: ParentView) in view }) != nil {
return parent
}

return nil
}

let target: OpaquePointer?
switch parent.storage {
case let .application(app):
target = app
case let .renderer(view):
target = view
if let view {
// Present the view content to the window
SDL_RenderPresent(view)
}
}

guard let target else { return nil }
return SDLTarget(host.view, target)
}

func update(
target: SDLTarget,
with host: MountedHost
) {
guard let view = mapAnyView(host.view, transform: { (target: AnySDL) in target })
else { return }

view.update(target: target)
}

func unmount(
target: SDLTarget,
from parent: SDLTarget,
with task: UnmountHostTask<SDLRenderer>
) {
defer { task.finish() }
guard mapAnyView(task.host.view, transform: { (target: AnySDL) in target }) != nil
else { return }
target.destroy()
}

public func isPrimitiveView(_ type: Any.Type) -> Bool {
type is SDLPrimitive.Type
}

public func primitiveBody(for view: Any) -> AnyView? {
(view as? SDLPrimitive)?.renderedBody
}
}

protocol SDLPrimitive {
var renderedBody: AnyView { get }
}
93 changes: 93 additions & 0 deletions Sources/TokamakSDL2/SDLTarget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import Foundation
import SDL
@_spi(TokamakCore) import TokamakCore

protocol AnySDL {
var expand: Bool { get }
func new(_ application: OpaquePointer) -> SDLTarget
func update(target: SDLTarget)
}

extension AnySDL {
var expand: Bool { false }
}

struct SDLView<Content: View>: View, AnySDL, ParentView {
let build: (OpaquePointer) -> SDLTarget
let update: (SDLTarget) -> ()
let content: Content
let expand: Bool

init(
build: @escaping (OpaquePointer) -> SDLTarget,
update: @escaping (SDLTarget) -> () = { _ in },
expand: Bool = false,
@ViewBuilder content: () -> Content
) {
self.build = build
self.expand = expand
self.content = content()
self.update = update
}

func new(_ application: OpaquePointer) -> SDLTarget {
build(application)
}

func update(target: SDLTarget) {
if case .renderer = target.storage {
update(target)
}
}

var body: Never {
neverBody("SDLView")
}

var children: [AnyView] {
[AnyView(content)]
}
}

extension SDLView where Content == EmptyView {
init(
build: @escaping (OpaquePointer) -> SDLTarget,
expand: Bool = false
) {
self.init(build: build, expand: expand) { EmptyView() }
}
}

final class SDLTarget: Target {
enum Storage {
case application(OpaquePointer?)
case renderer(OpaquePointer?)
}

let storage: Storage
var view: AnyView

init<V: View>(_ view: V, _ ref: OpaquePointer) {
storage = .renderer(ref)
self.view = AnyView(view)
}

init(renderer ref: OpaquePointer) {
storage = .renderer(ref)
view = AnyView(EmptyView())
}

init(window ref: OpaquePointer) {
storage = .application(ref)
view = AnyView(EmptyView())
}

func destroy() {
switch storage {
case .application:
fatalError("Attempt to destroy root Application.")
case let .renderer(target):
SDL_DestroyRenderer(target)
}
}
}

0 comments on commit cd37f1b

Please sign in to comment.