some files
This commit is contained in:
1 parent
08bc7a1b25
commit
7096f722d2
7 files changed
+403
No files matched your search
+51
@@ -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
|
||||||
@@ -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>
|
||||||
@@ -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.
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 },
|
||||||
|
|
||||||
|
])
|
||||||
|
}
|
||||||
Reference in new issue
Block a user