Create a static scene #1

Closed
kornerr wants to merge 37 commits from mahjong/mahjong:scn into master
pull from: mahjong/mahjong:scn
7 changed files with 403 additions and 0 deletions
Showing only changes of commit 7096f722d2 - Show all commits

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
version: 4
output:
ver-mac/src/dialect.swift:
type: swift
RootComponent:
type: component
oneliners:
- countText: print(c.countText)
- countText: RootVM.shared!.countText = c.countText
shoulds:
count:
1. Upon hitting 10 the first time:
if:
c.recentField == F.didClickIncrement &&
c.count == 7
then:
c.count += 10
2. Upon each button click:
if:
c.recentField == F.didClickIncrement
then:
c.count += 1
countText:
1. Upon each count change
if:
c.recentField == F.count
then:
c.countText = "Count: '\(c.count)'"
2. Upon launch
if:
c.recentField == F.didLaunch
then:
c.countText = "Press the button to count"
didLaunch:
1. Only once during the first setup:
if:
c.recentField == F.didSetup &&
c.didLaunch == false
then:
c.didLaunch = true
RootContext:
type: context
fields:
count: Int
countText: String
didClickIncrement: Bool
didLaunch: Bool
didSetup: Bool
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>OGS Mahjong</string>
<key>CFBundleDisplayName</key>
<string>OGS Mahjong</string>
<key>CFBundleIdentifier</key>
<string>org.opengamestudio.Mahjong</string>
<key>CFBundleExecutable</key>
<string>ogs-mahjong</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>
+15
View File
@@ -0,0 +1,15 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "OGSMahjong",
platforms: [
.macOS(.v13)
],
targets: [
.executableTarget(
name: "ogs-mahjong",
path: "src"
)
]
)
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
import AppKit
final class AppDelegate:
NSObject,
NSApplicationDelegate
{
let root = RootComponent()
let rootVM = RootVM()
func applicationDidFinishLaunching(_ notification: Notification) {
// Bring the app to the foreground
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
root.setup()
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
true
}
}
+14
View File
@@ -0,0 +1,14 @@
import AppKit
import SwiftUI
@main
struct GameApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
var body: some Scene {
WindowGroup {
RootView(appDelegate.rootVM)
}
.defaultSize(width: 640, height: 480)
}
}
+276
View File
@@ -0,0 +1,276 @@
// Reusable "none" constant
public let DIALECT_CONTEXT_RECENT_FIELD_NONE = "none"
// Base protocol for each component's state
public protocol DialectContext {
var recentField: String { get set }
func field<T>(_ name: String) -> T
func fieldAny(_ name: String) -> Any
mutating func setField(_ name: String, _ value: Any)
}
// Default implementation of `fieldAny` method for the protocol
public extension DialectContext {
func fieldAny(_ name: String) -> Any {
return field(name)
}
}
// The engine of the dialect components
public class DialectController {
var callbacks = [(DialectContext) -> Void]()
var context: DialectContext
var functions = [(DialectContext) -> DialectContext]()
var isProcessingQueue = false
var queue = [DialectContext]()
public init(_ context: DialectContext) {
self.context = context
}
func executeFunctions() {
let c = queue.removeFirst()
// Keep SSOT: Only allow single field change per should-function
context.recentField = c.recentField
context.setField(c.recentField, c.fieldAny(c.recentField))
for f in functions {
let ctx = f(context)
if ctx.recentField != DIALECT_CONTEXT_RECENT_FIELD_NONE {
queue.append(ctx)
}
}
reportContext()
}
func processQueue() {
// Prevent recursion.
if isProcessingQueue {
return
}
isProcessingQueue = true
while (queue.count > 0) {
executeFunctions()
}
isProcessingQueue = false
}
public func registerCallback(_ cb: @escaping (DialectContext) -> Void) {
callbacks.append(cb)
}
public func registerFieldCallback(
_ fieldName: String,
_ cb: @escaping (DialectContext) -> Void
) {
callbacks.append({ c in
if c.recentField == fieldName {
cb(c)
}
})
}
public func registerFunction(_ f: @escaping (DialectContext) -> DialectContext) {
functions.append(f)
}
func reportContext() {
for cb in callbacks {
cb(context)
}
}
public func set(
_ fieldName: String,
_ value: Any
) {
var c = context
c.setField(fieldName, value)
c.recentField = fieldName
queue.append(c)
processQueue()
}
}
// Register several oneliner callbacks to a controller
func registerOneliners<T>(
_ ctrl: DialectController,
_ items: [Any]
) -> T? {
let halfCount = items.count / 2
for i in 0..<halfCount {
let field = items[i * 2] as! String
let callback = items[i * 2 + 1] as! (T) -> Void
ctrl.registerFieldCallback(field) { cc in
let c = cc as! T
callback(c)
}
}
// A hack for generics to operate
return nil
}
// Context field names for static type check
struct F {
static let count = "count"
static let countText = "countText"
static let didClickIncrement = "didClickIncrement"
static let didLaunch = "didLaunch"
static let didSetup = "didSetup"
}
struct RootContext: DialectContext {
var count = Int()
var countText = String()
var didClickIncrement = Bool()
var didLaunch = Bool()
var didSetup = Bool()
var recentField = ""
func field<T>(_ name: String) -> T {
if (name == "count") {
return count as! T
}
else if (name == "countText") {
return countText as! T
}
else if (name == "didClickIncrement") {
return didClickIncrement as! T
}
else if (name == "didLaunch") {
return didLaunch as! T
}
else if (name == "didSetup") {
return didSetup as! T
}
return "unknown-field-name" as! T
}
mutating func setField(
_ name: String,
_ value: Any
) {
if (name == "count") {
count = value as! Int
}
else if (name == "countText") {
countText = value as! String
}
else if (name == "didClickIncrement") {
didClickIncrement = value as! Bool
}
else if (name == "didLaunch") {
didLaunch = value as! Bool
}
else if (name == "didSetup") {
didSetup = value as! Bool
}
}
}
func rootSet(
_ key: String,
_ value: Any
) {
RootComponent.singleton!.ctrl.set(key, value)
}
func rootShouldResetCount(_ c: RootContext) -> RootContext {
var c = c
/* 1. Upon hitting 10 the first time */
if
c.recentField == F.didClickIncrement &&
c.count == 7
{
c.count += 10
c.recentField = F.count
return c
}
/* 2. Upon each button click */
if
c.recentField == F.didClickIncrement
{
c.count += 1
c.recentField = F.count
return c
}
c.recentField = DIALECT_CONTEXT_RECENT_FIELD_NONE
return c
}
func rootShouldResetCountText(_ c: RootContext) -> RootContext {
var c = c
/* 1. Upon each count chang */
if
c.recentField == F.count
{
c.countText = "Count: '\(c.count)'"
c.recentField = F.countText
return c
}
/* 2. Upon launc */
if
c.recentField == F.didLaunch
{
c.countText = "Press the button to count"
c.recentField = F.countText
return c
}
c.recentField = DIALECT_CONTEXT_RECENT_FIELD_NONE
return c
}
func rootShouldResetDidLaunch(_ c: RootContext) -> RootContext {
var c = c
/* 1. Only once during the first setup */
if
c.recentField == F.didSetup &&
c.didLaunch == false
{
c.didLaunch = true
c.recentField = F.didLaunch
return c
}
c.recentField = DIALECT_CONTEXT_RECENT_FIELD_NONE
return c
}
func rootRegisterShoulds(_ ctrl: DialectController) {
[
rootShouldResetCount,
rootShouldResetCountText,
rootShouldResetDidLaunch,
].forEach { f in
ctrl.registerFunction { c in f(c as! RootContext) }
}
}
func rootRegisterEffects(_ ctrl: DialectController) {
let _: RootContext? = registerOneliners(ctrl, [
F.countText, { (c: RootContext) in print(c.countText) },
F.countText, { (c: RootContext) in RootVM.shared!.countText = c.countText },
])
}