Skip to content

Commit

Permalink
Preliminary support for function in script route
Browse files Browse the repository at this point in the history
  • Loading branch information
jean-bovet committed Dec 11, 2022
1 parent e34ec6e commit 7cd0814
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 15 deletions.
4 changes: 4 additions & 0 deletions BTrain.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
A75F41BE2944E78F003C709F /* MarklinInterfaceResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75F41BD2944E78F003C709F /* MarklinInterfaceResources.swift */; };
A75F41C0294509ED003C709F /* LocomotiveFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75F41BF294509ED003C709F /* LocomotiveFunctions.swift */; };
A75F41C529453C63003C709F /* LocomotiveFunctionsCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75F41C429453C63003C709F /* LocomotiveFunctionsCatalog.swift */; };
A75F41C729454A3A003C709F /* RouteScriptFunctionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75F41C629454A3A003C709F /* RouteScriptFunctionsView.swift */; };
A763F18D27BDD82A00B3499A /* TrainSpeedMeasurementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A763F18C27BDD82A00B3499A /* TrainSpeedMeasurementTests.swift */; };
A763F18F27BE01C300B3499A /* XCTestCase+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A763F18E27BE01C300B3499A /* XCTestCase+Extension.swift */; };
A763F19327BE26CF00B3499A /* LocomotiveSpeedMeasurementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A763F19227BE26CF00B3499A /* LocomotiveSpeedMeasurementsView.swift */; };
Expand Down Expand Up @@ -591,6 +592,7 @@
A75F41BD2944E78F003C709F /* MarklinInterfaceResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarklinInterfaceResources.swift; sourceTree = "<group>"; };
A75F41BF294509ED003C709F /* LocomotiveFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocomotiveFunctions.swift; sourceTree = "<group>"; };
A75F41C429453C63003C709F /* LocomotiveFunctionsCatalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocomotiveFunctionsCatalog.swift; sourceTree = "<group>"; };
A75F41C629454A3A003C709F /* RouteScriptFunctionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteScriptFunctionsView.swift; sourceTree = "<group>"; };
A763F18C27BDD82A00B3499A /* TrainSpeedMeasurementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrainSpeedMeasurementTests.swift; sourceTree = "<group>"; };
A763F18E27BE01C300B3499A /* XCTestCase+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Extension.swift"; sourceTree = "<group>"; };
A763F19227BE26CF00B3499A /* LocomotiveSpeedMeasurementsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocomotiveSpeedMeasurementsView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1505,6 +1507,7 @@
A7479680291F437E001DF7FA /* RouteScriptEditorView.swift */,
A747968629201FE4001DF7FA /* RouteScriptLineView.swift */,
A7479682292010F4001DF7FA /* RouteScriptCommandView.swift */,
A75F41C629454A3A003C709F /* RouteScriptFunctionsView.swift */,
);
path = RouteScript;
sourceTree = "<group>";
Expand Down Expand Up @@ -2149,6 +2152,7 @@
A744D1372765C5A3005C2F37 /* LayoutError.swift in Sources */,
A73EFD75292F3A260052C407 /* FeedbackPosition.swift in Sources */,
A71715892758448D00200F92 /* CGRect+Extension.swift in Sources */,
A75F41C729454A3A003C709F /* RouteScriptFunctionsView.swift in Sources */,
A75F41AF293D83E8003C709F /* RoutePicker.swift in Sources */,
A74F38052818C0E50013FD23 /* RouteItemTurnout.swift in Sources */,
A7BE36AA2751BDC300286709 /* ConnectorPlug.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions BTrain/Elements/Locomotive/LocomotiveFunctionsCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ final class LocomotiveFunctionsCatalog {

private var imageCache = NSCache<NSString, NSImage>()

var allTypes: [UInt32] {
type2attributes.keys.sorted()
}

func process(interface: CommandInterface) {
type2attributes.removeAll()
for attributes in interface.locomotiveFunctions() {
Expand Down
4 changes: 4 additions & 0 deletions BTrain/Scripts/RouteScript/RouteScript+Route.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ extension RouteScriptCommand {
items.append(contentsOf: routeItems)
}
return items

case .functions:
// TODO:
return []
}
}
}
Expand Down
13 changes: 12 additions & 1 deletion BTrain/Scripts/RouteScript/RouteScriptCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct RouteScriptCommand: ScriptCommand, Identifiable, Hashable {
case start = "Start"
case move = "Move"
case loop = "Repeat"
case functions = "Functions"
}

var action: Action = .move
Expand All @@ -38,6 +39,14 @@ struct RouteScriptCommand: ScriptCommand, Identifiable, Hashable {
var direction: Direction?
var stationId: Identifier<Station>?

struct Function: Identifiable, Hashable, Codable {
var id = UUID().uuidString
var type: UInt32
var enabled: Bool
}

var functions = [Function]()

init(id: UUID = UUID(), action: Action) {
self.id = id
self.action = action
Expand All @@ -46,7 +55,7 @@ struct RouteScriptCommand: ScriptCommand, Identifiable, Hashable {

extension RouteScriptCommand: Codable {
enum CodingKeys: CodingKey {
case id, action, children, repeatCount, waitDuration, destinationType, blockId, direction, stationId
case id, action, children, repeatCount, waitDuration, destinationType, blockId, direction, stationId, functions
}

init(from decoder: Decoder) throws {
Expand All @@ -61,6 +70,7 @@ extension RouteScriptCommand: Codable {
blockId = try container.decodeIfPresent(Identifier<Block>.self, forKey: CodingKeys.blockId)
direction = try container.decodeIfPresent(Direction.self, forKey: CodingKeys.direction)
stationId = try container.decodeIfPresent(Identifier<Station>.self, forKey: CodingKeys.stationId)
functions = try container.decodeIfPresent([Function].self, forKey: CodingKeys.functions) ?? []
}

func encode(to encoder: Encoder) throws {
Expand All @@ -74,5 +84,6 @@ extension RouteScriptCommand: Codable {
try container.encode(blockId, forKey: CodingKeys.blockId)
try container.encode(direction, forKey: CodingKeys.direction)
try container.encode(stationId, forKey: CodingKeys.stationId)
try container.encode(functions, forKey: CodingKeys.functions)
}
}
2 changes: 1 addition & 1 deletion BTrain/Views/Document/DocumentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ struct DocumentView: View {
case .layoutScripts:
LayoutScriptEditingView(doc: document, layout: document.layout)
case .routeScripts:
RouteScriptEditingView(layout: document.layout)
RouteScriptEditingView(doc: document, layout: document.layout)
case .routes:
RouteListView(layout: document.layout)
case .trains:
Expand Down
49 changes: 46 additions & 3 deletions BTrain/Views/Scripts/RouteScript/RouteScriptCommandView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
import SwiftUI

struct RouteScriptCommandView: View {
let doc: LayoutDocument
let layout: Layout
@ObservedObject var script: RouteScript
@Binding var command: RouteScriptCommand
@Binding var commandErrorIds: [String]

@State private var showFunctionsSheet = false

var body: some View {
HStack {
if command.action != .start {
Expand Down Expand Up @@ -79,9 +81,35 @@ struct RouteScriptCommandView: View {
Stepper("and wait \(command.waitDuration) seconds", value: $command.waitDuration, in: 0 ... 100, step: 10)
.fixedSize()
}

case .loop:
Stepper("\(command.repeatCount) times", value: $command.repeatCount, in: 1 ... 10)
.fixedSize()

case .functions:
if command.functions.isEmpty {
Button("Add…") {
showFunctionsSheet.toggle()
}
} else {
ForEach(command.functions, id: \.self) { function in
Group {
if let image = doc.locomotiveFunctionsCatalog.image(for: function.type) {
Image(nsImage: image)
.resizable()
.renderingMode(.template)
.frame(width: 20, height: 20)
} else {
Text("f\(function.type)")
}
}
.if(function.enabled, transform: { $0.foregroundColor(.yellow)})
.help(doc.locomotiveFunctionsCatalog.name(for: function.type) ?? "")
}
Button("Edit…") {
showFunctionsSheet.toggle()
}
}
}

Button("􀁌") {
Expand All @@ -93,6 +121,10 @@ struct RouteScriptCommandView: View {
script.commands.remove(source: command)
}.buttonStyle(.borderless)
}
}.sheet(isPresented: $showFunctionsSheet) {
RouteScriptFunctionsView(catalog: doc.locomotiveFunctionsCatalog, cmd: $command)
.padding()
.frame(width: 400, height: 300)
}
}
}
Expand Down Expand Up @@ -124,12 +156,23 @@ struct ScriptCommandView_Previews: PreviewProvider {
return cmd
}()

static let commands = [start, moveToFreeBlock, moveToStationBlock, loop]
static let emptyFunctions = {
var cmd = RouteScriptCommand(action: .functions)
return cmd
}()

static let functions = {
var cmd = RouteScriptCommand(action: .functions)
cmd.functions = [.init(type: 0, enabled: true), .init(type: 2, enabled: false)]
return cmd
}()

static let commands = [start, moveToFreeBlock, moveToStationBlock, loop, emptyFunctions, functions]

static var previews: some View {
VStack(alignment: .leading) {
ForEach(commands, id: \.self) { command in
RouteScriptCommandView(layout: doc.layout, script: script, command: .constant(command), commandErrorIds: .constant([]))
RouteScriptCommandView(doc: doc, layout: doc.layout, script: script, command: .constant(command), commandErrorIds: .constant([]))
.fixedSize()
}
}
Expand Down
9 changes: 6 additions & 3 deletions BTrain/Views/Scripts/RouteScript/RouteScriptEditingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import SwiftUI

struct RouteScriptEditingView: View {
let doc: LayoutDocument
@ObservedObject var layout: Layout

var body: some View {
Expand All @@ -31,7 +32,7 @@ struct RouteScriptEditingView: View {
TextField("", text: script.name)
}
}) { script in
RouteScriptEditorView(layout: layout, script: script)
RouteScriptEditorView(doc: doc, layout: layout, script: script)
}.onDisappear() {
layout.updateRoutesUsingRouteScripts()
}
Expand All @@ -42,12 +43,14 @@ struct RouteScriptEditingView_Previews: PreviewProvider {
static var previews: some View {
Group {
ConfigurationSheet(title: "Routes") {
RouteScriptEditingView(layout: RouteScriptEditorView_Previews.layout)
RouteScriptEditingView(doc: LayoutDocument(layout: RouteScriptEditorView_Previews.layout),
layout: RouteScriptEditorView_Previews.layout)
}
}
Group {
ConfigurationSheet(title: "Routes") {
RouteScriptEditingView(layout: Layout())
RouteScriptEditingView(doc: LayoutDocument(layout: Layout()),
layout: Layout())
}
}.previewDisplayName("Empty")
}
Expand Down
11 changes: 7 additions & 4 deletions BTrain/Views/Scripts/RouteScript/RouteScriptEditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import SwiftUI

struct RouteScriptEditorView: View {
let doc: LayoutDocument
let layout: Layout
@ObservedObject var script: RouteScript

Expand All @@ -31,12 +32,12 @@ struct RouteScriptEditorView: View {
} else {
List {
ForEach($script.commands, id: \.self) { command in
RouteScriptLineView(layout: layout, script: script, command: command, commandErrorIds: $validator.commandErrorIds)
RouteScriptLineView(doc: doc, layout: layout, script: script, command: command, commandErrorIds: $validator.commandErrorIds)
if let children = command.children {
ForEach(children, id: \.self) { command in
HStack {
Spacer().fixedSpace()
RouteScriptLineView(layout: layout, script: script, command: command, commandErrorIds: $validator.commandErrorIds)
RouteScriptLineView(doc: doc, layout: layout, script: script, command: command, commandErrorIds: $validator.commandErrorIds)
}
}
}
Expand Down Expand Up @@ -109,12 +110,14 @@ struct RouteScriptEditorView_Previews: PreviewProvider {
return layout
}()

static let doc = LayoutDocument(layout: layout)

static var previews: some View {
Group {
RouteScriptEditorView(layout: layout, script: layout.routeScripts[0])
RouteScriptEditorView(doc: doc, layout: layout, script: layout.routeScripts[0])
}.previewDisplayName("Route")
Group {
RouteScriptEditorView(layout: layout, script: RouteScript())
RouteScriptEditorView(doc: doc, layout: layout, script: RouteScript())
}.previewDisplayName("Empty Route")
}
}
120 changes: 120 additions & 0 deletions BTrain/Views/Scripts/RouteScript/RouteScriptFunctionsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2021-22 Jean Bovet
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import SwiftUI

struct RouteScriptFunctionsView: View {

let catalog: LocomotiveFunctionsCatalog
@Binding var cmd: RouteScriptCommand
@State private var editedCmd = RouteScriptCommand(action: .functions)

@Environment(\.presentationMode) var presentationMode

var body: some View {
VStack {
HStack {
Text("Functions to execute:")
Spacer()
}
if editedCmd.functions.isEmpty {
Spacer()
Button("Click 􀁌 To Add a Function") {
addFunction()
}
.buttonStyle(.borderless)
.padding()
Spacer()
} else {
List {
ForEach($editedCmd.functions, id:\.self) { function in
HStack {
Picker("State:", selection: function.enabled) {
Text("Enable").tag(true)
Text("Disable").tag(false)
}.labelsHidden()

Picker("Function:", selection: function.type) {
ForEach(catalog.allTypes, id: \.self) { type in
HStack {
if let name = catalog.name(for: type) {
Text("f\(type): \(name)")
} else {
Text("f\(type)")
}
if let image = catalog.image(for: type)?.copy(size: .init(width: 20, height: 20)) {
Image(nsImage: image)
.renderingMode(.template)
}
}.tag(type)
}
}.labelsHidden()

Button("􀁌") {
addFunction()
}.buttonStyle(.borderless)

Button("􀁎") {
removeFunction(function: function.wrappedValue)
}.buttonStyle(.borderless)
}
}
}
}
Divider()
HStack {
Spacer()

Button("Cancel") {
presentationMode.wrappedValue.dismiss()
}
.keyboardShortcut(.cancelAction)

Spacer().fixedSpace()

Button("OK") {
cmd = editedCmd
presentationMode.wrappedValue.dismiss()
}
.keyboardShortcut(.defaultAction)
}.padding()
}.onAppear() {
editedCmd = cmd
}
}

func addFunction() {
editedCmd.functions.append(.init(type: 0, enabled: false))
}

func removeFunction(function: RouteScriptCommand.Function) {
editedCmd.functions.removeAll(where: { $0.id == function.id })
}
}

struct RouteScriptFunctionsView_Previews: PreviewProvider {

static let cmd = {
var command = RouteScriptCommand(action: .functions)
command.functions = [.init(type: 0, enabled: true)]
return command
}()

static var previews: some View {
Group {
RouteScriptFunctionsView(catalog: LocomotiveFunctionsCatalog(), cmd: .constant(RouteScriptCommand(action: .functions)))
}.previewDisplayName("Empty")
Group {
RouteScriptFunctionsView(catalog: LocomotiveFunctionsCatalog(), cmd: .constant(cmd))
}.previewDisplayName("Functions")
}
}
Loading

0 comments on commit 7cd0814

Please sign in to comment.