Files
SpineParticlesWeb/connector/native/app_macos.mm
T
2026-09-06 00:38:59 +08:00

185 lines
7.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifdef __APPLE__
#import <AppKit/AppKit.h>
#include "config.hpp"
#include "connector_service.hpp"
#include "platform.hpp"
using connector::ConnectorConfig;
using connector::ConnectorService;
@interface ConnectorAppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate>
@property(nonatomic, strong) NSWindow* window;
@property(nonatomic, strong) NSTextField* pathField;
@property(nonatomic, strong) NSTextField* statusLabel;
@property(nonatomic, strong) NSArray<NSView*>* pathControls;
@property(nonatomic, strong) NSButton* connectionButton;
@property(nonatomic, assign) ConnectorService* service;
@property(nonatomic, copy) NSString* startupError;
@end
@implementation ConnectorAppDelegate
- (NSTextField*)label:(NSString*)text frame:(NSRect)frame size:(CGFloat)size color:(NSColor*)color {
NSTextField* label = [[NSTextField alloc] initWithFrame:frame];
label.stringValue = text;
label.editable = NO;
label.bezeled = NO;
label.drawsBackground = NO;
label.font = [NSFont systemFontOfSize:size];
label.textColor = color;
return label;
}
- (NSButton*)button:(NSString*)title frame:(NSRect)frame action:(SEL)action {
NSButton* button = [[NSButton alloc] initWithFrame:frame];
button.title = title;
button.bezelStyle = NSBezelStyleRounded;
button.target = self;
button.action = action;
return button;
}
- (void)applicationDidFinishLaunching:(NSNotification*)notification {
(void)notification;
self.window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 620, 326)
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable
backing:NSBackingStoreBuffered defer:NO];
self.window.title = @"Spine粒子连接器";
self.window.delegate = self;
[self.window center];
NSView* content = self.window.contentView;
[content addSubview:[self label:@"Spine粒子连接器" frame:NSMakeRect(28, 268, 560, 34) size:24 color:NSColor.labelColor]];
[content addSubview:[self label:@"应用打开且连接已启动时,网页可调用本机 Spine 生成工程文件。" frame:NSMakeRect(29, 237, 560, 24) size:14 color:NSColor.secondaryLabelColor]];
self.statusLabel = [self label:@"" frame:NSMakeRect(29, 201, 560, 24) size:14 color:NSColor.systemGreenColor];
[content addSubview:self.statusLabel];
NSTextField* pathLabel = [self label:@"Spine.com 路径" frame:NSMakeRect(29, 162, 130, 24) size:14 color:NSColor.labelColor];
[content addSubview:pathLabel];
self.pathField = [[NSTextField alloc] initWithFrame:NSMakeRect(29, 126, 390, 32)];
self.pathField.placeholderString = @"留空时自动检测 Spine";
const ConnectorConfig config = connector::load_config();
self.pathField.stringValue = [NSString stringWithUTF8String:config.spine_executable.c_str()];
[content addSubview:self.pathField];
NSButton* browse = [self button:@"选择 Spine.com" frame:NSMakeRect(430, 124, 160, 36) action:@selector(selectSpine:)];
[content addSubview:browse];
NSButton* automatic = [self button:@"恢复自动检测" frame:NSMakeRect(29, 78, 130, 36) action:@selector(clearPath:)];
[content addSubview:automatic];
NSButton* save = [self button:@"保存路径" frame:NSMakeRect(460, 78, 130, 36) action:@selector(savePath:)];
save.keyEquivalent = @"\r";
[content addSubview:save];
self.pathControls = @[pathLabel, self.pathField, browse, automatic, save];
self.connectionButton = [self button:@"断开连接" frame:NSMakeRect(460, 25, 130, 36) action:@selector(toggleConnection:)];
[content addSubview:self.connectionButton];
[self refreshStatus:nil];
[self.window makeKeyAndOrderFront:nil];
[NSApp activateIgnoringOtherApps:YES];
}
- (void)refreshStatus:(id)sender {
(void)sender;
self.connectionButton.title = self.service && self.service->running() ? @"断开连接" : @"启动连接";
if (self.startupError.length > 0) {
self.statusLabel.stringValue = [@"● 连接器未启动 · " stringByAppendingString:self.startupError];
self.statusLabel.textColor = NSColor.systemRedColor;
return;
}
if (!self.service || !self.service->running()) {
self.statusLabel.stringValue = @"● 连接器已断开";
self.statusLabel.textColor = NSColor.secondaryLabelColor;
return;
}
const std::string spine = connector::find_spine_executable();
const BOOL showPathControls = spine.empty();
for (NSView* view in self.pathControls) view.hidden = !showPathControls;
self.statusLabel.stringValue = spine.empty() ? @"● 连接器运行中 · 未找到 Spine" : @"● 连接器运行中 · Spine 已就绪";
self.statusLabel.textColor = spine.empty() ? NSColor.systemOrangeColor : NSColor.systemGreenColor;
}
- (void)toggleConnection:(id)sender {
(void)sender;
if (self.service && self.service->running()) {
self.service->stop();
self.startupError = @"";
} else if (self.service) {
std::string error;
self.service->start(error);
self.startupError = [NSString stringWithUTF8String:error.c_str()];
}
[self refreshStatus:nil];
}
- (void)selectSpine:(id)sender {
(void)sender;
NSOpenPanel* panel = [NSOpenPanel openPanel];
panel.canChooseFiles = YES;
panel.canChooseDirectories = NO;
panel.allowsMultipleSelection = NO;
panel.message = @"请选择 Spine 命令行可执行文件(macOS 通常为 Spine.app/Contents/MacOS/Spine";
if ([panel runModal] == NSModalResponseOK) self.pathField.stringValue = panel.URL.path;
}
- (void)clearPath:(id)sender {
(void)sender;
self.pathField.stringValue = @"";
[self savePath:nil];
}
- (void)savePath:(id)sender {
(void)sender;
const std::string path = self.pathField.stringValue.UTF8String ?: "";
if (!path.empty() && !connector::valid_spine_executable(path)) {
NSAlert* alert = [[NSAlert alloc] init];
alert.messageText = @"Spine 路径无效";
alert.informativeText = @"请选择实际存在的 Spine 命令行可执行文件。";
[alert runModal];
return;
}
try {
ConnectorConfig config = connector::load_config();
config.spine_executable = path;
connector::save_config(config);
[self refreshStatus:nil];
} catch (const std::exception& error) {
NSAlert* alert = [[NSAlert alloc] init];
alert.messageText = @"保存失败";
alert.informativeText = [NSString stringWithUTF8String:error.what()];
[alert runModal];
}
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender {
(void)sender;
return YES;
}
- (void)applicationWillTerminate:(NSNotification*)notification {
(void)notification;
if (self.service) self.service->stop();
}
@end
int main(int argc, char* argv[]) {
(void)argc;
(void)argv;
@autoreleasepool {
ConnectorService service;
std::string startup_error;
service.start(startup_error);
NSApplication* application = [NSApplication sharedApplication];
application.activationPolicy = NSApplicationActivationPolicyRegular;
application.appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
ConnectorAppDelegate* delegate = [[ConnectorAppDelegate alloc] init];
delegate.service = &service;
delegate.startupError = [NSString stringWithUTF8String:startup_error.c_str()];
application.delegate = delegate;
[application run];
service.stop();
}
return 0;
}
#endif