hostile-takeover/game/sdl/mac/chatviewcontroller.mm
Nathan Fulton fff71efc11 SDL-OSX port improvements
- Add chatroom
- Add string input
- Update main() to support SDL2
- Change game files location to
~/Library/ApplicationSupport/HostileTakeover
- Open URLs with OS’s default browser
- Use Xcode default C/C++Objective-C compiler
- Remove game/sdl/mac/SDL.framework
- Use game/sdl/SDL2/osx/SDL2.framework
2016-08-24 16:19:45 -04:00

442 lines
15 KiB
Plaintext

#import "game/sdl/mac/chatViewController.h"
#import "game/sdl/mac/chatcell.h"
@implementation ChatViewController
#define TEXTFIELDWIDTH_LANDSCAPE view_.bounds.size.width - 81
#define TEXTFIELDHEIGHT_LANDSCAPE 24
#define TITLELABELWIDTH_LANDSCAPE 100
#define TOOLBAR_HEIGHT 30
#define CHAT_HISTORY 100
#define CHAT_QUEUE 100
- (id)init {
wi::HostHelpers::GetSurfaceProperties(&pprops_);
NSRect windowFrame = NSMakeRect(window_.screen.frame.size.width/2 - pprops_.cxWidth/2, window_.screen.frame.size.height/2 - pprops_.cyHeight/2, pprops_.cxWidth, pprops_.cyHeight);
window_ = [[NSWindow alloc] initWithContentRect:windowFrame
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:YES];
view_ = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, pprops_.cxWidth, pprops_.cyHeight)];
tableView_ = nil;
toolbar2_ = nil;
toolbar_ = nil;
chatEntries_ = [[NSMutableArray alloc] initWithCapacity:CHAT_HISTORY];
userEntries_ = [[NSMutableArray alloc] initWithCapacity:CHAT_HISTORY];
title_ = @"Chat";
titleLabel_ = nil;
textField_ = nil;
entryFont_ = [NSFont systemFontOfSize:12];
NSSize size10Spaces = [@" " sizeWithAttributes: @{NSFontAttributeName:entryFont_}];
width10Spaces_ = size10Spaces.width;
chatc_ = NULL; clear_ = YES;
[self loadView];
return self;
}
- (void)loadView {
// Window
[window_ setMovable:YES];
[window_ setLevel:NSNormalWindowLevel];
[window_.contentView addSubview:view_];
[window_ setTitle:@"Hostile Takeover - Chat"];
// View
[view_ setWantsLayer:YES];
// Toolbar (bottom toolbar)
toolbar_ = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, view_.frame.size.width, TOOLBAR_HEIGHT)];
toolbar_.autoresizingMask = NSViewWidthSizable | NSViewMaxYMargin;
toolbar_.autoresizesSubviews = YES;
[view_ addSubview:toolbar_];
// Textfield
// TODO: Considder using NSTextView instead of NSTextField to allow vertically resizing the view
// when typing longer chats
textField_ = [[NSTextField alloc] initWithFrame:NSZeroRect];
textField_.textColor = [NSColor blackColor];
textField_.placeholderString = @"";
[textField_ setTarget:self];
[textField_ setAction:@selector(onSend)];
[textField_ setFocusRingType:NSFocusRingTypeNone];
[toolbar_ addSubview:textField_];
// Toolbar2 (top toolbar)
toolbar2_ = [[NSView alloc] initWithFrame:NSMakeRect(0, view_.frame.size.height - TOOLBAR_HEIGHT, view_.frame.size.width, TOOLBAR_HEIGHT)];
toolbar2_.autoresizingMask = NSViewWidthSizable | NSViewMaxYMargin;
toolbar2_.autoresizesSubviews = YES;
[view_ addSubview:toolbar2_];
// Title Label
titleLabel_ = [[NSTextView alloc] initWithFrame:NSZeroRect];
[titleLabel_ setFont:[NSFont boldSystemFontOfSize:18]];
[titleLabel_ setTextColor:[NSColor whiteColor]];
[titleLabel_ setString:title_];
[titleLabel_ setAlignment:NSCenterTextAlignment];
[titleLabel_ setSelectable:NO];
[titleLabel_ setBackgroundColor:[NSColor clearColor]];
// Tableview -- also see [self reCreateTableView]
tableView_ = [[NSTableView alloc] initWithFrame:NSZeroRect];
// tableView_ gets set as the document for scrollView and then scrollView is added
// as a subview of view_. So tableView_ doesnt need to be added as a subview of view_.
// [view_ addSubview:tableView_];
[self layoutViews];
}
- (void)layoutViews {
// Send button
NSButton *sendButton = [[NSButton alloc] initWithFrame:NSZeroRect];
[sendButton setTitle:@"Send"];
[sendButton setTarget:self];
[sendButton setAction:@selector(onSend)];
[sendButton sizeToFit];
[toolbar_ addSubview:sendButton];
// Players button
NSButton *playersButton = [[NSButton alloc] initWithFrame:NSZeroRect];
[playersButton setTitle:@"Players"];
[playersButton setTarget:self];
[playersButton setAction:@selector(onPlayers)];
[playersButton sizeToFit];
[playersButton setFrameOrigin:NSMakePoint(toolbar2_.frame.size.width - playersButton.frame.size.width - 5, toolbar2_.frame.size.height/2 - playersButton.frame.size.height/2)];
[toolbar2_ addSubview:playersButton];
// Done button
NSButton *doneButton = [[NSButton alloc] initWithFrame:NSZeroRect];
[doneButton setTitle:@"Done"];
[doneButton setTarget:self];
[doneButton setAction:@selector(onDone)];
[doneButton sizeToFit];
[doneButton setFrameOrigin:NSMakePoint(5, toolbar2_.frame.size.height/2 - doneButton.frame.size.height/2)];
[toolbar2_ addSubview:doneButton];
// Textfield & send button (subviews of toolbar_)
[textField_ setFrame:NSMakeRect(5, toolbar_.frame.size.height/2 - sendButton.frame.size.height/2, toolbar_.frame.size.width - sendButton.frame.size.width - 15, sendButton.frame.size.height)];
[sendButton setFrameOrigin:NSMakePoint(textField_.frame.size.width + 10, textField_.frame.origin.y)];
// Title label
[titleLabel_ setFrameSize:NSMakeSize(toolbar2_.frame.size.width/2, toolbar2_.frame.size.height)];
[titleLabel_ setFrameOrigin:NSMakePoint(toolbar2_.frame.size.width/2 - titleLabel_.frame.size.width/2, toolbar2_.frame.size.height/2 - titleLabel_.frame.size.height/2)];
[toolbar2_ addSubview:titleLabel_];
// Add some color
[toolbar_.layer setBackgroundColor:[[NSColor darkGrayColor] CGColor]];
[toolbar2_.layer setBackgroundColor:[[NSColor darkGrayColor] CGColor]];
[titleLabel_ setBackgroundColor:[NSColor clearColor]];
}
- (void)reCreateTableView {
// Table Column
tableColumn_ = [[NSTableColumn alloc] initWithIdentifier:@"chatColumn"];
[tableColumn_ setWidth:view_.frame.size.width];
// Table View
tableView_ = [[NSTableView alloc] initWithFrame:NSMakeRect(0,
toolbar_.frame.size.height,
view_.frame.size.width,
view_.frame.size.height - toolbar_.frame.size.height - toolbar2_.frame.size.height)];
[tableView_ setDelegate:self];
[tableView_ setDataSource:self];
[tableView_ setFocusRingType: NSFocusRingTypeNone];
[tableView_ addTableColumn:tableColumn_];
[tableView_ setHeaderView:nil];
[tableView_ setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleNone];
// Scroll view
NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect(0,
toolbar_.frame.size.height,
view_.frame.size.width,
view_.frame.size.height - toolbar_.frame.size.height - toolbar2_.frame.size.height)];
[scrollView setHasVerticalScroller:YES];
[scrollView setHasHorizontalScroller:NO];
[scrollView setDocumentView:tableView_];
[view_ addSubview:scrollView];
// Both adding tableColumn_ to tableView_ and setting tableView as scroll view's document
// seems to change tableView_'s height. So let's change that back to view_'s width.
[tableView_ setFrameSize:NSMakeSize(view_.frame.size.width, tableView_.frame.size.height)];
}
- (void)show {
BOOL created = NO;
if (clear_ || tableView_ == nil) {
// Create the table view. It will automagically reload the model
[self reCreateTableView];
clear_ = NO;
created = YES;
}
// Present the chat window
[window_ setFrameOrigin:[[[NSApplication sharedApplication] mainWindow] frame].origin];
[window_ makeKeyAndOrderFront:self];
[textField_ becomeFirstResponder];
// Only scroll to the bottom if the table view existed already.
// Otherwise it will crash, because data model loading is
// asynchronous.
if (created == NO) {
[self scrollToBottom];
}
}
- (void)onDone {
[textField_ resignFirstResponder];
[window_ orderOut:self];
if (chatc_ != NULL) {
chatc_->thread().Post(kidmOnDismissed, chatc_);
}
}
- (void)onSend {
if (chatc_ == NULL) {
return;
}
NSString *text = textField_.stringValue;
int count = (int)text.length;
int index = 0;
for (; index < count; index++) {
unichar ch = [text characterAtIndex:index];
if (ch != 0) {
break;
}
}
text = [[text substringFromIndex:index] stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceCharacterSet]];
if (text.length != 0) {
NSData *data = [text
dataUsingEncoding:[NSString defaultCStringEncoding]
allowLossyConversion:YES];
std::string s((const char *)[data bytes], [data length]);
wi::ChatParams *params = new wi::ChatParams(s);
chatc_->thread().Post(kidmOnSend, chatc_, params);
}
[textField_ setStringValue:@""];
}
- (void)onPlayers {
if (chatc_ != NULL) {
chatc_->thread().Post(kidmOnPlayers, chatc_);
}
}
- (void)scrollToBottom {
[tableView_ beginUpdates];
// Scroll to the bottom
int index = (int)[chatEntries_ count] - 1;
if (index >= 0) {
[tableView_ scrollRowToVisible:[tableView_ numberOfRows] - 1];
}
[tableView_ endUpdates];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [chatEntries_ count];
}
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row {
return [self heightForString:[chatEntries_ objectAtIndex:row] width:view_.frame.size.width];
}
- (NSView *)tableView:(NSTableView *)tableView
viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
ChatCell *cell = [ChatCell cellWithRect:NSMakeRect(0, 0,
view_.frame.size.width,
[self heightForString:[chatEntries_ objectAtIndex:row] width:view_.frame.size.width])
user:[userEntries_ objectAtIndex:row]
chat:[chatEntries_ objectAtIndex:row]];
return cell;
}
- (float)heightForString:(NSString *)string width:(float)width {
return [self sizeForString:string maxWidth:width].height;
}
- (NSSize)sizeForString:(NSString *)string maxWidth:(float)width {
NSFont* font = [NSFont systemFontOfSize:12];
// Calculate the size for the text to be able to display correctly
NSString* text = string;
NSInteger maxWidth = width;
NSInteger maxHeight = 2000;
NSSize constraint = NSMakeSize(maxWidth, maxHeight);
NSDictionary* attrs = [NSDictionary dictionaryWithObjectsAndKeys:NSFontAttributeName, font, nil];
NSRect newBounds = [text boundingRectWithSize:constraint
options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin
attributes:attrs];
return newBounds.size;
}
- (void)updateChatRows:(BOOL)scroll {
[tableView_ reloadData];
[self scrollToBottom];
}
- (void)setController:(wi::ChatController *)chatc {
chatc_ = chatc;
}
- (wi::ChatController *)controller {
return chatc_;
}
- (void)doClear
{
[chatEntries_ removeAllObjects];
[userEntries_ removeAllObjects];
[self updateChatRows:NO];
}
- (void)doAddChat:(NSDictionary *)dict
{
NSString *chat = [dict objectForKey:@"chat"];
NSString *user = [dict objectForKey:@"player"];
if ([user length] == 0) {
// System message.
[chatEntries_ addObject:chat];
[userEntries_ addObject:user];
} else {
// Regular chat message. Add ':' to the end of user
NSString *newUser = [NSString stringWithFormat:@"%@: ", user];
// Insert spaces into chat so that user's name can draw into that
// space with a different UILabel. Hack-o-rama.
NSSize sizeUser = [self sizeForString:newUser maxWidth:200];
CGFloat ratio = sizeUser.width / width10Spaces_;
int countSpaces = ceil(ratio * 10.0) + 2;
char szSpaces[256];
if (countSpaces > sizeof(szSpaces)) {
countSpaces = sizeof(szSpaces);
}
memset(szSpaces, ' ', sizeof(szSpaces));
szSpaces[countSpaces - 1] = 0;
NSString *newChat = [NSString stringWithFormat:@"%s%@", szSpaces,
chat];
[chatEntries_ addObject:newChat];
[userEntries_ addObject:newUser];
}
[self updateChatRows:YES];
}
- (void)doShow
{
[self show];
[titleLabel_ setString:title_];
}
- (void)doHide
{
[self onDone];
}
- (void)doSetTitle:(NSString *)title
{
title_ = title;
}
@end
// ++++
// This is the game thread callable class for controlling chat
namespace wi {
// These get called on the game thread, and do the real work on the main
// thread.
ChatController::ChatController(ChatViewController *vcchat) : vcchat_(vcchat), pcccb_(NULL) {
vcchat_ = vcchat;
[vcchat_ setController:this];
}
void ChatController::Clear() {
[vcchat_ performSelectorOnMainThread:@selector(doClear) withObject:nil waitUntilDone: NO];
}
void ChatController::AddChat(const char *player, const char *chat) {
printf("ChatController::AddChat %s\n", chat);
@autoreleasepool {
NSString *player_s = [NSString stringWithCString:player
encoding:[NSString defaultCStringEncoding]];
NSString *chat_s = [NSString stringWithCString:chat
encoding:[NSString defaultCStringEncoding]];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
player_s, @"player", chat_s, @"chat", (char *)NULL];
[vcchat_ performSelectorOnMainThread:@selector(doAddChat:)
withObject:dict waitUntilDone: NO];
}
}
void ChatController::Show(bool fShow) {
printf("ChatController::Show %s\n", fShow ? "True" : "False");
if (fShow) {
[vcchat_ performSelectorOnMainThread:@selector(doShow)
withObject:nil waitUntilDone: NO];
} else {
[vcchat_ performSelectorOnMainThread:@selector(doHide)
withObject:nil waitUntilDone: NO];
}
}
void ChatController::SetTitle(const char *title) {
printf("ChatController::SetTitle %s\n", title);
title_ = title;
NSString *title_s = [NSString stringWithCString:title
encoding:[NSString defaultCStringEncoding]];
[vcchat_ performSelectorOnMainThread:@selector(doSetTitle:)
withObject:title_s waitUntilDone: NO];
}
const char *ChatController::GetTitle() {
return title_.c_str();
}
IChatControllerCallback *ChatController::SetCallback(
IChatControllerCallback *pcccb) {
IChatControllerCallback *old = pcccb_;
pcccb_ = pcccb;
return old;
}
void ChatController::OnMessage(base::Message *pmsg) {
printf("ChatController::OnMessage\n");
switch (pmsg->id) {
case kidmOnDismissed:
if (pcccb_ != NULL) {
pcccb_->OnChatDismissed();
}
break;
case kidmOnSend:
if (pcccb_ != NULL) {
ChatParams *params = (ChatParams *)pmsg->data;
pcccb_->OnChatSend(params->chat.c_str());
delete params;
}
break;
case kidmOnPlayers:
if (pcccb_ != NULL) {
pcccb_->OnPlayers();
}
break;
}
}
} // namespace wi