Skip to content

Commit aa6228e

Browse files
committed
Add layout command
1 parent ad2eb53 commit aa6228e

File tree

7 files changed

+394
-39
lines changed

7 files changed

+394
-39
lines changed

app/src/Outlander.xcodeproj/project.pbxproj

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@
7575
7B3B9A1B2759DB200035D731 /* IconLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3B9A192759DB200035D731 /* IconLoader.swift */; };
7676
7B3B9A1D275B11390035D731 /* SubsCommandHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3B9A1C275B11390035D731 /* SubsCommandHandler.swift */; };
7777
7B3B9A1E275B11390035D731 /* SubsCommandHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3B9A1C275B11390035D731 /* SubsCommandHandler.swift */; };
78+
7B5DD3D12DD25686009C1D99 /* LayoutCommandHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5DD3D02DD25686009C1D99 /* LayoutCommandHandler.swift */; };
79+
7B5DD3D22DD25686009C1D99 /* LayoutCommandHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5DD3D02DD25686009C1D99 /* LayoutCommandHandler.swift */; };
7880
7B6820472C8BC856009833B3 /* EmulateTextCommandHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6820462C8BC856009833B3 /* EmulateTextCommandHandler.swift */; };
7981
7B6820482C8BC856009833B3 /* EmulateTextCommandHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6820462C8BC856009833B3 /* EmulateTextCommandHandler.swift */; };
8082
7B8F887327924B5200713BA7 /* AddProfileWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8F887127924B5200713BA7 /* AddProfileWindow.swift */; };
@@ -147,6 +149,7 @@
147149
7BBBC51F2772C4E800D1CF3E /* EditCommandHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBC51D2772C4E800D1CF3E /* EditCommandHandler.swift */; };
148150
7BBBC5212776912700D1CF3E /* DateFormats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBC5202776912700D1CF3E /* DateFormats.swift */; };
149151
7BBBC5222776912700D1CF3E /* DateFormats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBC5202776912700D1CF3E /* DateFormats.swift */; };
152+
7BC4DE252DD63AE500BE2592 /* LayoutCommandHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC4DE242DD63AE500BE2592 /* LayoutCommandHandlerTests.swift */; };
150153
7BC74D1B2755D37300B6DF62 /* LinkCommandHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC74D1A2755D37300B6DF62 /* LinkCommandHandler.swift */; };
151154
7BC74D1C2755D37300B6DF62 /* LinkCommandHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC74D1A2755D37300B6DF62 /* LinkCommandHandler.swift */; };
152155
7BC74D1D27575B4300B6DF62 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9BCF2ED22E177E100C7F7F0 /* AppDelegate.swift */; };
@@ -358,6 +361,7 @@
358361
7B3B9A1827596F390035D731 /* TestPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = TestPlayground.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
359362
7B3B9A192759DB200035D731 /* IconLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconLoader.swift; sourceTree = "<group>"; };
360363
7B3B9A1C275B11390035D731 /* SubsCommandHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubsCommandHandler.swift; sourceTree = "<group>"; };
364+
7B5DD3D02DD25686009C1D99 /* LayoutCommandHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutCommandHandler.swift; sourceTree = "<group>"; };
361365
7B6820462C8BC856009833B3 /* EmulateTextCommandHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmulateTextCommandHandler.swift; sourceTree = "<group>"; };
362366
7B8F887127924B5200713BA7 /* AddProfileWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProfileWindow.swift; sourceTree = "<group>"; };
363367
7B8F887227924B5200713BA7 /* AddProfileWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AddProfileWindow.xib; sourceTree = "<group>"; };
@@ -395,6 +399,7 @@
395399
7BBBC51A276DD0F800D1CF3E /* AtomicQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicQueue.swift; sourceTree = "<group>"; };
396400
7BBBC51D2772C4E800D1CF3E /* EditCommandHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCommandHandler.swift; sourceTree = "<group>"; };
397401
7BBBC5202776912700D1CF3E /* DateFormats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormats.swift; sourceTree = "<group>"; };
402+
7BC4DE242DD63AE500BE2592 /* LayoutCommandHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutCommandHandlerTests.swift; sourceTree = "<group>"; };
398403
7BC74D1A2755D37300B6DF62 /* LinkCommandHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkCommandHandler.swift; sourceTree = "<group>"; };
399404
7BCBBD0C273F499D00219CA0 /* VariableReplacerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariableReplacerTests.swift; sourceTree = "<group>"; };
400405
7BCBBD0E2743ABEA00219CA0 /* VariableTokenizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariableTokenizer.swift; sourceTree = "<group>"; };
@@ -792,6 +797,7 @@
792797
F62EA130247A2846004E2C1E /* GagCommandHandler.swift */,
793798
7B228B22273880320052610B /* GotoCommandHandler.swift */,
794799
7B10A68D275FE538000FEDE8 /* HighlightsCommandHandler.swift */,
800+
7B5DD3D02DD25686009C1D99 /* LayoutCommandHandler.swift */,
795801
7BC74D1A2755D37300B6DF62 /* LinkCommandHandler.swift */,
796802
7B956B4E2754A55D00346019 /* LogCommandHandler.swift */,
797803
7BA0E7D428DE739300A1F319 /* MacroCommandHandler.swift */,
@@ -821,6 +827,7 @@
821827
7B956B40274F8AD900346019 /* CommandProcessorTests.swift */,
822828
F9CA20552466066400A92736 /* EchoCommandHandlerTests.swift */,
823829
F62EA140247BE6EB004E2C1E /* GagCommandHandlerTests.swift */,
830+
7BC4DE242DD63AE500BE2592 /* LayoutCommandHandlerTests.swift */,
824831
F9CA205D24664A4500A92736 /* VarCommandHandlerTests.swift */,
825832
F9CA206C2466765D00A92736 /* WindowCommandHandlerTests.swift */,
826833
);
@@ -1067,6 +1074,7 @@
10671074
7BBBC5212776912700D1CF3E /* DateFormats.swift in Sources */,
10681075
F969A0EB239C812900408CE5 /* WindowViewController.swift in Sources */,
10691076
F98B491D2470AF17000E2D92 /* GameContext.swift in Sources */,
1077+
7B5DD3D12DD25686009C1D99 /* LayoutCommandHandler.swift in Sources */,
10701078
F98B4909246F9668000E2D92 /* HighlightLoader.swift in Sources */,
10711079
7BAC840325E08C2F00A6C7D5 /* ScriptRunner.swift in Sources */,
10721080
7BCE4A3125C508C200AF0444 /* MapperCommandHandler.swift in Sources */,
@@ -1131,6 +1139,7 @@
11311139
7B10A59B275C1F26000FEDE8 /* LocalHost.swift in Sources */,
11321140
F9D36E9422EF76E8008BC6E6 /* GameStream.swift in Sources */,
11331141
F9BCF31F22E28EC200C7F7F0 /* Socket.swift in Sources */,
1142+
7B5DD3D22DD25686009C1D99 /* LayoutCommandHandler.swift in Sources */,
11341143
7B917E4A2763F7A200887F95 /* PrintBox.swift in Sources */,
11351144
7B228B272738BA080052610B /* SwiftPriorityQueue.swift in Sources */,
11361145
7B956B502754A55D00346019 /* LogCommandHandler.swift in Sources */,
@@ -1236,6 +1245,7 @@
12361245
7B27C5B1274B23760095B9BF /* ScriptContext.swift in Sources */,
12371246
7BCBBD162746D1D800219CA0 /* ExpressionEvaluator.swift in Sources */,
12381247
7B917E5D27641E5300887F95 /* SimplePing.m in Sources */,
1248+
7BC4DE252DD63AE500BE2592 /* LayoutCommandHandlerTests.swift in Sources */,
12391249
F9D78D3123A9E3A500FE2343 /* PresetLoader.swift in Sources */,
12401250
F9B253FA247874BE0097C07F /* ProfileConfigLoaderTests.swift in Sources */,
12411251
7B3B9A1E275B11390035D731 /* SubsCommandHandler.swift in Sources */,
@@ -1437,7 +1447,7 @@
14371447
CODE_SIGN_IDENTITY = "Apple Development";
14381448
CODE_SIGN_STYLE = Automatic;
14391449
COMBINE_HIDPI_IMAGES = YES;
1440-
CURRENT_PROJECT_VERSION = 52;
1450+
CURRENT_PROJECT_VERSION = 53;
14411451
DEAD_CODE_STRIPPING = YES;
14421452
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
14431453
DEVELOPMENT_TEAM = 9TZ225FMST;
@@ -1473,7 +1483,7 @@
14731483
CODE_SIGN_IDENTITY = "Apple Development";
14741484
CODE_SIGN_STYLE = Automatic;
14751485
COMBINE_HIDPI_IMAGES = YES;
1476-
CURRENT_PROJECT_VERSION = 52;
1486+
CURRENT_PROJECT_VERSION = 53;
14771487
DEAD_CODE_STRIPPING = YES;
14781488
DEVELOPMENT_TEAM = 9TZ225FMST;
14791489
ENABLE_HARDENED_RUNTIME = YES;

app/src/Outlander/Handlers/CommandProcessor.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class CommandProcesssor {
6161
handlers.append(TriggerCommandHandler(files))
6262
handlers.append(EditCommandHandler(files))
6363
handlers.append(MacroCommandHandler(files))
64+
handlers.append(LayoutCommandHandler(files))
6465
// for local testing only
6566
// handlers.append(EmulateTextCommandHandler(files))
6667

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// LayoutCommandHandler.swift
3+
// Outlander
4+
//
5+
// Created by Joe McBride on 5/12/25.
6+
// Copyright © 2025 Joe McBride. All rights reserved.
7+
//
8+
9+
import AppKit
10+
import Foundation
11+
12+
public class LayoutCommandHandler: ICommandHandler {
13+
14+
private let files: FileSystem
15+
private let log = LogManager.getLog(String(describing: LayoutCommandHandler.self))
16+
17+
var command = "#layout"
18+
19+
let validCommands = ["load", "reload", "save", "settings"]
20+
let loadCommands = ["load", "reload"]
21+
22+
init(_ files: FileSystem) {
23+
self.files = files
24+
}
25+
26+
func handle(_ input: String, with context: GameContext) {
27+
let commands = input.split(separator: " ", maxSplits: 1)
28+
var text = [String]()
29+
if commands.count > 1 {
30+
text = commands[1].trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: " ")
31+
}
32+
33+
guard text.count > 0 else {
34+
let msg = text.joined()
35+
context.events2.echoError("'\(msg)' is not a valid layout command")
36+
return
37+
}
38+
39+
var command = "load"
40+
var fileName = text[0]
41+
if (validCommands.contains(text[0].lowercased())) {
42+
command = text[0].lowercased()
43+
fileName = text.count > 1 ? text[1] : context.applicationSettings.profile.layout
44+
}
45+
46+
if (command == "settings") {
47+
context.events2.toggleLayoutSettings()
48+
return
49+
}
50+
51+
if (!fileName.hasSuffix(".cfg")) {
52+
fileName = "\(fileName).cfg"
53+
}
54+
55+
let filePath = context.applicationSettings.paths.layout.appendingPathComponent(fileName)
56+
57+
if loadCommands.contains(command), !files.fileExists(filePath) {
58+
context.events2.echoError("Layout '\(fileName)' does not exist")
59+
return
60+
}
61+
62+
switch command {
63+
case "save":
64+
context.events2.saveLayout(fileName)
65+
context.events2.echoText("Saved layout: \(fileName)")
66+
return
67+
case "reload", "load":
68+
context.events2.loadLayout(fileName)
69+
context.events2.echoText("Loaded layout: \(fileName)")
70+
return
71+
default:
72+
return
73+
}
74+
}
75+
}

app/src/Outlander/Infrastructure/Events.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ struct EmulatedTextEvent: Event {
6363
var data: String
6464
}
6565

66+
struct LoadLayoutEvent: Event {
67+
var layout: String
68+
}
69+
70+
struct SaveLayoutEvent: Event {
71+
var layout: String
72+
}
73+
74+
struct ToggleLayoutSettingsEvent: Event {
75+
}
76+
6677
extension Events2 {
6778
func echoText(_ text: String, preset: String? = nil, color: String? = nil, mono: Bool = false) {
6879
let data = EchoTextEvent(text: "\(text)\n".hexDecoededString(), preset: preset, color: color, mono: mono)
@@ -98,6 +109,21 @@ extension Events2 {
98109
let evt = EmulatedTextEvent(data: data)
99110
post(evt)
100111
}
112+
113+
func loadLayout(_ layout: String) {
114+
let evt = LoadLayoutEvent(layout: layout)
115+
post(evt)
116+
}
117+
118+
func saveLayout(_ layout: String) {
119+
let evt = SaveLayoutEvent(layout: layout)
120+
post(evt)
121+
}
122+
123+
func toggleLayoutSettings() {
124+
let evt = ToggleLayoutSettingsEvent()
125+
post(evt)
126+
}
101127
}
102128

103129
class DummyEvent<T> where T: BaseEvent {}

app/src/Outlander/UI/GameViewController.swift

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,18 @@ class GameViewController: NSViewController, NSWindowDelegate, IUICommandHandler
311311
self.handleRawStream(data: evt.data, streamData: true)
312312
}
313313

314+
gameContext.events2.register(self) { (evt: LoadLayoutEvent) in
315+
self.loadLayout(evt.layout)
316+
}
317+
318+
gameContext.events2.register(self) { (evt: SaveLayoutEvent) in
319+
self.saveLayout(evt.layout)
320+
}
321+
322+
gameContext.events2.register(self) { (evt: ToggleLayoutSettingsEvent) in
323+
self.toggleLayoutSettings()
324+
}
325+
314326
vitalsBar.presetFor = { name in
315327
guard self.vitalsBar.enabled == true else {
316328
return (self.vitalsBar.disabledForegroundColor, self.vitalsBar.disabledBackgroundColor)
@@ -515,6 +527,7 @@ class GameViewController: NSViewController, NSWindowDelegate, IUICommandHandler
515527
logText("Profile: \(gameContext.applicationSettings.currentProfilePath.path)\n", mono: false, playerCommand: false)
516528
logText("Maps: \(gameContext.applicationSettings.paths.maps.path)\n", mono: false, playerCommand: false)
517529
logText("Scripts: \(gameContext.applicationSettings.paths.scripts.path)\n", mono: false, playerCommand: false)
530+
logText("Layouts: \(gameContext.applicationSettings.paths.layout.path)\n", mono: false, playerCommand: false)
518531
logText("Logs: \(gameContext.applicationSettings.paths.logs.path)\n", mono: false, playerCommand: false)
519532
}
520533

@@ -530,26 +543,43 @@ class GameViewController: NSViewController, NSWindowDelegate, IUICommandHandler
530543
updateWindowTitle()
531544
}
532545

533-
public func command(_ command: String) {
534-
if command == "layout:LoadDefault" {
535-
gameContext.applicationSettings.profile.layout = "default.cfg"
536-
gameContext.layout = windowLayoutLoader?.load(gameContext.applicationSettings, file: "default.cfg")
537-
reloadWindows("default.cfg") {
538-
self.reloadTheme()
546+
func loadLayout(_ layoutName: String) {
547+
gameContext.applicationSettings.profile.layout = layoutName
548+
gameContext.layout = windowLayoutLoader?.load(gameContext.applicationSettings, file: layoutName)
549+
reloadWindows(layoutName) {
550+
self.reloadTheme()
551+
}
552+
}
553+
554+
func saveLayout(_ layoutName: String) {
555+
gameContext.applicationSettings.profile.layout = layoutName
556+
let layout = buildWindowsLayout()
557+
for l in layout.windows {
558+
print("\(l.name) \(l.order)")
559+
}
560+
windowLayoutLoader?.save(
561+
gameContext.applicationSettings,
562+
file: layoutName,
563+
windows: layout
564+
)
565+
}
566+
567+
func toggleLayoutSettings() {
568+
for (_, win) in gameWindows {
569+
if win.visible {
570+
win.toggleSettings()
539571
}
572+
}
573+
}
540574

575+
public func command(_ command: String) {
576+
if command == "layout:LoadDefault" {
577+
self.loadLayout("default.cfg")
541578
return
542579
}
543580

544581
if command == "layout:SaveDefault" {
545-
gameContext.applicationSettings.profile.layout = "default.cfg"
546-
let layout = buildWindowsLayout()
547-
windowLayoutLoader?.save(
548-
gameContext.applicationSettings,
549-
file: "default.cfg",
550-
windows: layout
551-
)
552-
582+
saveLayout("default.cfg")
553583
return
554584
}
555585

@@ -568,11 +598,7 @@ class GameViewController: NSViewController, NSWindowDelegate, IUICommandHandler
568598
openPanel.nameFieldStringValue = gameContext.applicationSettings.profile.layout
569599

570600
if let url = openPanel.runModal() == .OK ? openPanel.urls.first : nil {
571-
gameContext.applicationSettings.profile.layout = url.lastPathComponent
572-
gameContext.layout = windowLayoutLoader?.load(gameContext.applicationSettings, file: url.lastPathComponent)
573-
reloadWindows(url.lastPathComponent) {
574-
self.reloadTheme()
575-
}
601+
self.loadLayout(url.lastPathComponent)
576602
}
577603

578604
return
@@ -588,28 +614,14 @@ class GameViewController: NSViewController, NSWindowDelegate, IUICommandHandler
588614
savePanel.directoryURL = gameContext.applicationSettings.paths.layout
589615

590616
if let url = savePanel.runModal() == .OK ? savePanel.url : nil {
591-
gameContext.applicationSettings.profile.layout = url.lastPathComponent
592-
593-
let layout = buildWindowsLayout()
594-
for l in layout.windows {
595-
print("\(l.name) \(l.order)")
596-
}
597-
windowLayoutLoader?.save(
598-
gameContext.applicationSettings,
599-
file: url.lastPathComponent,
600-
windows: layout
601-
)
617+
self.saveLayout(url.lastPathComponent)
602618
}
603619

604620
return
605621
}
606622

607623
if command == "layout:Settings" {
608-
for (_, win) in gameWindows {
609-
if win.visible {
610-
win.toggleSettings()
611-
}
612-
}
624+
self.toggleLayoutSettings()
613625
return
614626
}
615627

@@ -807,7 +819,7 @@ class GameViewController: NSViewController, NSWindowDelegate, IUICommandHandler
807819
}
808820

809821
DispatchQueue.main.async {
810-
self.logText("Loaded layout \(file)\n", mono: true, playerCommand: false)
822+
self.logText("Loaded layout: \(file)\n", mono: true, playerCommand: false)
811823
callback?()
812824
}
813825
}

0 commit comments

Comments
 (0)