iOS: Split up general interop and launcher backend

This commit is contained in:
UnknownShadow200 2024-06-29 07:49:26 +10:00
parent 308bd12fad
commit 89e47e7a2a
4 changed files with 742 additions and 675 deletions

View file

@ -13,6 +13,7 @@
9A62ADF5286D906F00E5E3DE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A62ADF4286D906F00E5E3DE /* Assets.xcassets */; };
9A6C79652BFDDEF200676D27 /* FancyLighting.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A6C79642BFDDEF100676D27 /* FancyLighting.c */; };
9A6C79672BFDDF0700676D27 /* Queue.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A6C79662BFDDF0600676D27 /* Queue.c */; };
9A6C7DFA2C2F610C00676D27 /* LBackend_ios.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A6C7DF92C2F610C00676D27 /* LBackend_ios.m */; };
9A7401D92B737D5C0040E575 /* Commands.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A7401D82B737D5B0040E575 /* Commands.c */; };
9A7401DB2B7384060040E575 /* SSL.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A7401DA2B7384060040E575 /* SSL.c */; };
9A89D4F227F802F600FF3F80 /* LWidgets.c in Sources */ = {isa = PBXBuildFile; fileRef = 9A89D37827F802F500FF3F80 /* LWidgets.c */; };
@ -101,6 +102,7 @@
9A62ADF4286D906F00E5E3DE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ClassiCube/Assets.xcassets; sourceTree = "<group>"; };
9A6C79642BFDDEF100676D27 /* FancyLighting.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = FancyLighting.c; sourceTree = "<group>"; };
9A6C79662BFDDF0600676D27 /* Queue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Queue.c; sourceTree = "<group>"; };
9A6C7DF92C2F610C00676D27 /* LBackend_ios.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LBackend_ios.m; sourceTree = "<group>"; };
9A7401D82B737D5B0040E575 /* Commands.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Commands.c; sourceTree = "<group>"; };
9A7401DA2B7384060040E575 /* SSL.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SSL.c; sourceTree = "<group>"; };
9A89D35727F802B100FF3F80 /* ClassiCube.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ClassiCube.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -216,6 +218,7 @@
9A89D37727F802F500FF3F80 /* src */ = {
isa = PBXGroup;
children = (
9A6C7DF92C2F610C00676D27 /* LBackend_ios.m */,
9A6C79662BFDDF0600676D27 /* Queue.c */,
9A6C79642BFDDEF100676D27 /* FancyLighting.c */,
9A4D0C632BDD168800E1695D /* TouchUI.c */,
@ -448,6 +451,7 @@
9A89D57427F802F600FF3F80 /* MapRenderer.c in Sources */,
9A89D57627F802F600FF3F80 /* _pshinter.c in Sources */,
9A89D56A27F802F600FF3F80 /* Physics.c in Sources */,
9A6C7DFA2C2F610C00676D27 /* LBackend_ios.m in Sources */,
9A89D58727F802F600FF3F80 /* SelectionBox.c in Sources */,
9A89D4FE27F802F600FF3F80 /* Inventory.c in Sources */,
9A89D56527F802F600FF3F80 /* Generator.c in Sources */,

View file

@ -443,6 +443,7 @@ static void DrawTriangle2D(Vertex* V0, Vertex* V1, Vertex* V2) {
A = PackedCol_A(color);
}
if (gfx_alphaTest && A < 0x80) continue;
if (gfx_alphaBlend) {
BitmapCol dst = colorBuffer[cb_index];
int dstR = BitmapCol_R(dst);
@ -453,7 +454,6 @@ static void DrawTriangle2D(Vertex* V0, Vertex* V1, Vertex* V2) {
G = (G * A + dstG * (255 - A)) >> 8;
B = (B * A + dstB * (255 - A)) >> 8;
}
if (gfx_alphaTest && A < 0x80) continue;
colorBuffer[cb_index] = BitmapCol_Make(R, G, B, 0xFF);
}
@ -554,7 +554,9 @@ static void DrawTriangle3D(Vertex* V0, Vertex* V1, Vertex* V2) {
A = PackedCol_A(color);
}
if (gfx_alphaTest && A < 0x80) continue;
int cb_index = y * cb_stride + x;
if (gfx_alphaBlend) {
BitmapCol dst = colorBuffer[cb_index];
int dstR = BitmapCol_R(dst);
@ -565,7 +567,6 @@ static void DrawTriangle3D(Vertex* V0, Vertex* V1, Vertex* V2) {
G = (G * A + dstG * (255 - A)) >> 8;
B = (B * A + dstB * (255 - A)) >> 8;
}
if (gfx_alphaTest && A < 0x80) continue;
if (depthWrite) depthBuffer[db_index] = z;
colorBuffer[cb_index] = BitmapCol_Make(R, G, B, 0xFF);

709
src/LBackend_ios.m Normal file
View file

@ -0,0 +1,709 @@
#include "Core.h"
#if defined CC_BUILD_IOS
#include "Bitmap.h"
#include "Input.h"
#include "Platform.h"
#include "String.h"
#include "Errors.h"
#include "Drawer2D.h"
#include "Launcher.h"
#include "LBackend.h"
#include "LWidgets.h"
#include "LScreens.h"
#include "Gui.h"
#include "LWeb.h"
#include "Funcs.h"
#include "Window.h"
#include <UIKit/UIKit.h>
#include <CoreText/CoreText.h>
#ifdef TARGET_OS_TV
// NSFontAttributeName etc - iOS 6.0
#define TEXT_ATTRIBUTE_FONT NSFontAttributeName
#define TEXT_ATTRIBUTE_COLOR NSForegroundColorAttributeName
#else
// UITextAttributeFont etc - iOS 5.0
#define TEXT_ATTRIBUTE_FONT UITextAttributeFont
#define TEXT_ATTRIBUTE_COLOR UITextAttributeTextColor
#endif
// shared state with interop_ios.m
extern UITextField* kb_widget;
extern CGContextRef win_ctx;
extern UIView* view_handle;
UIColor* ToUIColor(BitmapCol color, float A);
NSString* ToNSString(const cc_string* text);
void LInput_SetKeyboardType(UITextField* fld, int flags);
void LInput_SetPlaceholder(UITextField* fld, const char* placeholder);
/*########################################################################################################################*
*------------------------------------------------------Common helpers--------------------------------------------------------*
*#########################################################################################################################*/
static NSMutableAttributedString* ToAttributedString(const cc_string* text) {
// NSMutableAttributedString - iOS 3.2
cc_string left = *text, part;
char colorCode = 'f';
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] init];
while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode))
{
BitmapCol color = Drawer2D_GetColor(colorCode);
NSString* bit = ToNSString(&part);
NSDictionary* attrs =
@{
//TEXT_ATTRIBUTE_FONT : font,
TEXT_ATTRIBUTE_COLOR : ToUIColor(color, 1.0f)
};
NSAttributedString* attr_bit = [[NSAttributedString alloc] initWithString:bit attributes:attrs];
[str appendAttributedString:attr_bit];
}
return str;
}
static UIColor* GetStringColor(const cc_string* text) {
cc_string left = *text, part;
char colorCode = 'f';
Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode);
BitmapCol color = Drawer2D_GetColor(colorCode);
return ToUIColor(color, 1.0f);
}
static NSString* GetColorlessString(const cc_string* text) {
char buffer[128];
cc_string tmp = String_FromArray(buffer);
String_AppendColorless(&tmp, text);
return ToNSString(&tmp);
}
static void FreeContents(void* info, const void* data, size_t size) { Mem_Free(data); }
// TODO probably a better way..
static UIImage* ToUIImage(struct Bitmap* bmp) {
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGDataProviderRef provider;
CGImageRef image;
provider = CGDataProviderCreateWithData(NULL, bmp->scan0,
Bitmap_DataSize(bmp->width, bmp->height), FreeContents);
image = CGImageCreate(bmp->width, bmp->height, 8, 32, bmp->width * 4, colorspace,
kCGBitmapByteOrder32Host | kCGImageAlphaNoneSkipFirst, provider, NULL, 0, 0);
UIImage* img = [UIImage imageWithCGImage:image];
CGImageRelease(image);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorspace);
return img;
}
/*########################################################################################################################*
*------------------------------------------------------UI Backend--------------------------------------------------------*
*#########################################################################################################################*/
static struct LWidget* FindWidgetForView(id obj) {
struct LScreen* s = Launcher_Active;
for (int i = 0; i < s->numWidgets; i++)
{
void* meta = s->widgets[i]->meta;
if (meta != (__bridge void*)obj) continue;
return s->widgets[i];
}
return NULL;
}
static void LTable_UpdateCellColor(UIView* view, struct ServerInfo* server, int row, cc_bool selected);
static void LTable_UpdateCell(UITableView* table, UITableViewCell* cell, int row);
static NSString* cellID = @"CC_Cell";
@interface CCUIController : NSObject<UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
@end
@implementation CCUIController
- (void)handleButtonPress:(id)sender {
struct LWidget* w = FindWidgetForView(sender);
if (!w) return;
struct LButton* btn = (struct LButton*)w;
btn->OnClick(btn);
}
- (void)handleTextChanged:(id)sender {
struct LWidget* w = FindWidgetForView(sender);
if (!w) return;
UITextField* src = (UITextField*)sender;
const char* str = src.text.UTF8String;
struct LInput* ipt = (struct LInput*)w;
ipt->text.length = 0;
String_AppendUtf8(&ipt->text, str, String_Length(str));
if (ipt->TextChanged) ipt->TextChanged(ipt);
}
- (void)handleValueChanged:(id)sender {
UISwitch* swt = (UISwitch*)sender;
UIView* parent = swt.superview;
struct LWidget* w = FindWidgetForView(parent);
if (!w) return;
struct LCheckbox* cb = (struct LCheckbox*)w;
cb->value = [swt isOn];
if (!cb->ValueChanged) return;
cb->ValueChanged(cb);
}
// === UITableViewDataSource ===
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// cellForRowAtIndexPath - iOS 2.0
//UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID];
}
LTable_UpdateCell(tableView, cell, (int)indexPath.row);
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// numberOfRowsInSection - iOS 2.0
struct LTable* w = (struct LTable*)FindWidgetForView(tableView);
return w ? w->rowsCount : 0;
}
// === UITableViewDelegate ===
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// didSelectRowAtIndexPath - iOS 2.0
int row = (int)indexPath.row;
struct ServerInfo* server = LTable_Get(row);
LTable_UpdateCellColor([tableView cellForRowAtIndexPath:indexPath], server, row, true);
struct LTable* w = (struct LTable*)FindWidgetForView(tableView);
if (!w) return;
LTable_RowClick(w, row);
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
// didDeselectRowAtIndexPath - iOS 2.0
int row = (int)indexPath.row;
struct ServerInfo* server = LTable_Get(row);
LTable_UpdateCellColor([tableView cellForRowAtIndexPath:indexPath], server, row, false);
}
// === UITextFieldDelegate ===
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
// textFieldShouldReturn - iOS 2.0
struct LWidget* w = FindWidgetForView(textField);
if (!w) return YES;
struct LWidget* sel = Launcher_Active->onEnterWidget;
if (sel && !w->skipsEnter) {
sel->OnClick(sel);
} else {
[textField resignFirstResponder];
}
return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
// textFieldDidBeginEditing - iOS 2.0
kb_widget = textField;
}
@end
static CCUIController* ui_controller;
void LBackend_Init(void) {
ui_controller = [[CCUIController alloc] init];
CFBridgingRetain(ui_controller); // prevent GC TODO even needed?
}
void LBackend_MarkDirty(void* widget) { }
void LBackend_Tick(void) { }
void LBackend_Free(void) { }
void LBackend_UpdateTitleFont(void) { }
static void DrawText(NSAttributedString* str, struct Context2D* ctx, int x, int y) {
// CTLineCreateWithAttributedString - iOS 3.2
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)str);
CGRect bounds = CTLineGetImageBounds(line, win_ctx);
int centreX = (int)(ctx->width / 2.0f - bounds.size.width / 2.0f);
CGContextSetTextPosition(win_ctx, centreX + x, ctx->height - y);
CTLineDraw(line, win_ctx);
}
void LBackend_DrawTitle(struct Context2D* ctx, const char* title) {
if (Launcher_BitmappedText()) {
struct FontDesc font;
Launcher_MakeTitleFont(&font);
Launcher_DrawTitle(&font, title, ctx);
// bitmapped fonts don't need to be freed
return;
}
// systemFontOfSize: - iOS 2.0
UIFont* font = [UIFont systemFontOfSize:42];
NSString* text = [NSString stringWithCString:title encoding:NSASCIIStringEncoding];
NSDictionary* attrs_bg =
@{
TEXT_ATTRIBUTE_FONT : font,
TEXT_ATTRIBUTE_COLOR : UIColor.blackColor
};
NSAttributedString* str_bg = [[NSAttributedString alloc] initWithString:text attributes:attrs_bg];
DrawText(str_bg, ctx, 4, 42);
NSDictionary* attrs_fg =
@{
TEXT_ATTRIBUTE_FONT : font,
TEXT_ATTRIBUTE_COLOR : UIColor.whiteColor
};
NSAttributedString* str_fg = [[NSAttributedString alloc] initWithString:text attributes:attrs_fg];
DrawText(str_fg, ctx, 0, 38);
}
void LBackend_InitFramebuffer(void) { }
void LBackend_FreeFramebuffer(void) { }
void LBackend_Redraw(void) {
struct Context2D ctx;
struct Bitmap bmp;
int width = max(Window_Main.Width, 1);
int height = max(Window_Main.Height, 1);
Window_AllocFramebuffer(&bmp, width, height);
Context2D_Wrap(&ctx, &bmp);
Launcher_Active->DrawBackground(Launcher_Active, &ctx);
Rect2D rect = { 0, 0, width, height };
Window_DrawFramebuffer(rect, &bmp);
Window_FreeFramebuffer(&bmp);
}
static void LBackend_ButtonUpdateBackground(struct LButton* w);
void LBackend_ThemeChanged(void) {
struct LScreen* s = Launcher_Active;
LBackend_Redraw();
for (int i = 0; i < s->numWidgets; i++)
{
struct LWidget* w = s->widgets[i];
if (w->type != LWIDGET_BUTTON) continue;
LBackend_ButtonUpdateBackground((struct LButton*)w);
}
}
/*########################################################################################################################*
*------------------------------------------------------ButtonWidget-------------------------------------------------------*
*#########################################################################################################################*/
static void LBackend_ButtonUpdateBackground(struct LButton* w) {
UIButton* btn = (__bridge UIButton*)w->meta;
CGRect rect = [btn frame];
int width = (int)rect.size.width;
int height = (int)rect.size.height;
// memory freeing deferred until UIImage is freed (see FreeContents)
struct Bitmap bmp1, bmp2;
struct Context2D ctx1, ctx2;
Bitmap_Allocate(&bmp1, width, height);
Context2D_Wrap(&ctx1, &bmp1);
LButton_DrawBackground(&ctx1, 0, 0, width, height, false);
[btn setBackgroundImage:ToUIImage(&bmp1) forState:UIControlStateNormal];
Bitmap_Allocate(&bmp2, width, height);
Context2D_Wrap(&ctx2, &bmp2);
LButton_DrawBackground(&ctx2, 0, 0, width, height, true);
[btn setBackgroundImage:ToUIImage(&bmp2) forState:UIControlStateHighlighted];
}
void LBackend_ButtonInit(struct LButton* w, int width, int height) {
w->_textWidth = width;
w->_textHeight = height;
}
static UIView* LBackend_ButtonShow(struct LButton* w) {
UIButton* btn = [[UIButton alloc] init];
btn.frame = CGRectMake(0, 0, w->_textWidth, w->_textHeight);
[btn addTarget:ui_controller action:@selector(handleButtonPress:) forControlEvents:UIControlEventTouchUpInside];
w->meta = (__bridge void*)btn;
LBackend_ButtonUpdateBackground(w);
LBackend_ButtonUpdate(w);
return btn;
}
void LBackend_ButtonUpdate(struct LButton* w) {
UIButton* btn = (__bridge UIButton*)w->meta;
UIColor* color = GetStringColor(&w->text);
[btn setTitleColor:color forState:UIControlStateNormal];
NSString* str = GetColorlessString(&w->text);
[btn setTitle:str forState:UIControlStateNormal];
}
void LBackend_ButtonDraw(struct LButton* w) { }
/*########################################################################################################################*
*-----------------------------------------------------CheckboxWidget------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_CheckboxInit(struct LCheckbox* w) { }
static UIView* LBackend_CheckboxShow(struct LCheckbox* w) {
UIView* root = [[UIView alloc] init];
CGRect frame;
UISwitch* swt = [[UISwitch alloc] init];
[swt addTarget:ui_controller action:@selector(handleValueChanged:) forControlEvents:UIControlEventValueChanged];
UILabel* lbl = [[UILabel alloc] init];
lbl.backgroundColor = UIColor.clearColor;
lbl.textColor = UIColor.whiteColor;
lbl.text = ToNSString(&w->text);
[lbl sizeToFit]; // adjust label to fit text
[root addSubview:swt];
[root addSubview:lbl];
// label should be slightly to right of switch and vertically centred
frame = lbl.frame;
frame.origin.x = swt.frame.size.width + 10.0f;
frame.origin.y = swt.frame.size.height / 2 - frame.size.height / 2;
lbl.frame = frame;
// adjust root view height to enclose children
frame = root.frame;
frame.size.width = lbl.frame.origin.x + lbl.frame.size.width;
frame.size.height = max(swt.frame.size.height, lbl.frame.size.height);
root.frame = frame;
//root.userInteractionEnabled = YES;
w->meta = (__bridge void*)root;
LBackend_CheckboxUpdate(w);
return root;
}
void LBackend_CheckboxUpdate(struct LCheckbox* w) {
UIView* root = (__bridge UIView*)w->meta;
UISwitch* swt = (UISwitch*)root.subviews[0];
swt.on = w->value;
}
void LBackend_CheckboxDraw(struct LCheckbox* w) { }
/*########################################################################################################################*
*------------------------------------------------------InputWidget--------------------------------------------------------*
*#########################################################################################################################*/
void LInput_SetKeyboardType(UITextField* fld, int flags) {
int type = flags & 0xFF;
if (type == KEYBOARD_TYPE_INTEGER) {
fld.keyboardType = UIKeyboardTypeNumberPad;
} else if (type == KEYBOARD_TYPE_PASSWORD) {
fld.secureTextEntry = YES;
}
if (flags & KEYBOARD_FLAG_SEND) {
fld.returnKeyType = UIReturnKeySend;
} else {
fld.returnKeyType = UIReturnKeyDone;
}
}
void LInput_SetPlaceholder(UITextField* fld, const char* placeholder) {
if (!placeholder) return;
cc_string hint = String_FromReadonly(placeholder);
fld.placeholder = ToNSString(&hint);
}
void LBackend_InputInit(struct LInput* w, int width) {
w->_textHeight = width;
}
static UIView* LBackend_InputShow(struct LInput* w) {
UITextField* fld = [[UITextField alloc] init];
fld.frame = CGRectMake(0, 0, w->_textHeight, LINPUT_HEIGHT);
fld.borderStyle = UITextBorderStyleBezel;
fld.backgroundColor = UIColor.whiteColor;
fld.textColor = UIColor.blackColor;
fld.delegate = ui_controller;
[fld addTarget:ui_controller action:@selector(handleTextChanged:) forControlEvents:UIControlEventEditingChanged];
LInput_SetKeyboardType(fld, w->inputType);
LInput_SetPlaceholder(fld, w->hintText);
w->meta = (__bridge void*)fld;
LBackend_InputUpdate(w);
return fld;
}
void LBackend_InputUpdate(struct LInput* w) {
UITextField* fld = (__bridge UITextField*)w->meta;
fld.text = ToNSString(&w->text);
}
void LBackend_InputDraw(struct LInput* w) { }
void LBackend_InputTick(struct LInput* w) { }
void LBackend_InputSelect(struct LInput* w, int idx, cc_bool wasSelected) { }
void LBackend_InputUnselect(struct LInput* w) { }
/*########################################################################################################################*
*------------------------------------------------------LabelWidget--------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_LabelInit(struct LLabel* w) { }
static UIView* LBackend_LabelShow(struct LLabel* w) {
UILabel* lbl = [[UILabel alloc] init];
w->meta = (__bridge void*)lbl;
lbl.backgroundColor = UIColor.clearColor;
if (w->small) lbl.font = [UIFont systemFontOfSize:14.0f];
LBackend_LabelUpdate(w);
return lbl;
}
void LBackend_LabelUpdate(struct LLabel* w) {
UILabel* lbl = (__bridge UILabel*)w->meta;
if (!lbl) return;
if ([lbl respondsToSelector:@selector(attributedText)]) {
// attributedText - iOS 6.0
lbl.attributedText = ToAttributedString(&w->text);
} else {
lbl.textColor = GetStringColor(&w->text);
lbl.text = GetColorlessString(&w->text);
}
[lbl sizeToFit]; // adjust label to fit text
}
void LBackend_LabelDraw(struct LLabel* w) { }
/*########################################################################################################################*
*-------------------------------------------------------LineWidget--------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_LineInit(struct LLine* w, int width) {
w->_width = width;
}
static UIView* LBackend_LineShow(struct LLine* w) {
UIView* view = [[UIView alloc] init];
view.frame = CGRectMake(0, 0, w->_width, LLINE_HEIGHT);
w->meta = (__bridge void*)view;
BitmapCol color = LLine_GetColor();
view.backgroundColor = ToUIColor(color, 0.5f);
return view;
}
void LBackend_LineDraw(struct LLine* w) { }
/*########################################################################################################################*
*------------------------------------------------------SliderWidget-------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_SliderInit(struct LSlider* w, int width, int height) {
w->_width = width;
w->_height = height;
}
static UIView* LBackend_SliderShow(struct LSlider* w) {
UIProgressView* prg = [[UIProgressView alloc] init];
prg.frame = CGRectMake(0, 0, w->_width, w->_height);
prg.progressTintColor = ToUIColor(w->color, 1.0f);
w->meta = (__bridge void*)prg;
return prg;
}
void LBackend_SliderUpdate(struct LSlider* w) {
UIProgressView* prg = (__bridge UIProgressView*)w->meta;
prg.progress = w->value / 100.0f;
}
void LBackend_SliderDraw(struct LSlider* w) { }
/*########################################################################################################################*
*------------------------------------------------------TableWidget-------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_TableInit(struct LTable* w) { }
static UIView* LBackend_TableShow(struct LTable* w) {
UITableView* tbl = [[UITableView alloc] init];
tbl.delegate = ui_controller;
tbl.dataSource = ui_controller;
tbl.editing = NO;
tbl.allowsSelection = YES;
LTable_UpdateCellColor(tbl, NULL, 1, false);
//[tbl registerClass:UITableViewCell.class forCellReuseIdentifier:cellID];
w->meta = (__bridge void*)tbl;
return tbl;
}
void LBackend_TableUpdate(struct LTable* w) {
UITableView* tbl = (__bridge UITableView*)w->meta;
[tbl reloadData];
}
void LBackend_TableDraw(struct LTable* w) { }
void LBackend_TableReposition(struct LTable* w) { }
void LBackend_TableMouseDown(struct LTable* w, int idx) { }
void LBackend_TableMouseUp(struct LTable* w, int idx) { }
void LBackend_TableMouseMove(struct LTable* w, int idx) { }
static void LTable_UpdateCellColor(UIView* view, struct ServerInfo* server, int row, cc_bool selected) {
BitmapCol color = LTable_RowColor(row, selected, server && server->featured);
if (color) {
view.backgroundColor = ToUIColor(color, 1.0f);
view.opaque = YES;
} else {
view.backgroundColor = UIColor.clearColor;
view.opaque = NO;
}
}
static void LTable_UpdateCell(UITableView* table, UITableViewCell* cell, int row) {
struct ServerInfo* server = LTable_Get(row);
struct Flag* flag = Flags_Get(server);
char descBuffer[128];
cc_string desc = String_FromArray(descBuffer);
String_Format2(&desc, "%i/%i players, up for ", &server->players, &server->maxPlayers);
LTable_FormatUptime(&desc, server->uptime);
if (server->software.length) String_Format1(&desc, " | %s", &server->software);
if (flag && flag->meta)
cell.imageView.image = (__bridge UIImage*)flag->meta;
cell.textLabel.text = ToNSString(&server->name);
cell.detailTextLabel.text = ToNSString(&desc);//[ToNSString(&desc) stringByAppendingString:@"\nLine2"];
cell.textLabel.textColor = UIColor.whiteColor;
cell.detailTextLabel.textColor = UIColor.whiteColor;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
NSIndexPath* sel = table.indexPathForSelectedRow;
cc_bool selected = sel && sel.row == row;
LTable_UpdateCellColor(cell, server, row, selected);
}
// TODO only redraw flags
static void OnFlagsChanged(void) {
struct LScreen* s = Launcher_Active;
for (int i = 0; i < s->numWidgets; i++)
{
if (s->widgets[i]->type != LWIDGET_TABLE) continue;
UITableView* tbl = (__bridge UITableView*)s->widgets[i]->meta;
// trying to update cell.imageView.image doesn't seem to work,
// so pointlessly reload entire table data instead
NSIndexPath* selected = [tbl indexPathForSelectedRow];
[tbl reloadData];
[tbl selectRowAtIndexPath:selected animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
/*########################################################################################################################*
*------------------------------------------------------UI Backend--------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_DecodeFlag(struct Flag* flag, cc_uint8* data, cc_uint32 len) {
NSData* ns_data = [NSData dataWithBytes:data length:len];
UIImage* img = [UIImage imageWithData:ns_data];
if (!img) return;
flag->meta = CFBridgingRetain(img);
OnFlagsChanged();
}
static void LBackend_LayoutDimensions(struct LWidget* w, CGRect* r) {
const struct LLayout* l = w->layouts + 2;
while (l->type)
{
switch (l->type)
{
case LLAYOUT_WIDTH:
r->size.width = Window_Main.Width - (int)r->origin.x - Display_ScaleX(l->offset);
break;
case LLAYOUT_HEIGHT:
r->size.height = Window_Main.Height - (int)r->origin.y - Display_ScaleY(l->offset);
break;
}
l++;
}
}
void LBackend_LayoutWidget(struct LWidget* w) {
const struct LLayout* l = w->layouts;
UIView* view = (__bridge UIView*)w->meta;
CGRect r = [view frame];
int width = (int)r.size.width;
int height = (int)r.size.height;
r.origin.x = Gui_CalcPos(l[0].type & 0xFF, Display_ScaleX(l[0].offset), width, Window_Main.Width);
r.origin.y = Gui_CalcPos(l[1].type & 0xFF, Display_ScaleY(l[1].offset), height, Window_Main.Height);
// e.g. Table widget needs adjusts width/height based on window
if (l[1].type & LLAYOUT_EXTRA)
LBackend_LayoutDimensions(w, &r);
view.frame = r;
}
static UIView* ShowWidget(struct LWidget* w) {
switch (w->type)
{
case LWIDGET_BUTTON:
return LBackend_ButtonShow((struct LButton*)w);
case LWIDGET_CHECKBOX:
return LBackend_CheckboxShow((struct LCheckbox*)w);
case LWIDGET_INPUT:
return LBackend_InputShow((struct LInput*)w);
case LWIDGET_LABEL:
return LBackend_LabelShow((struct LLabel*)w);
case LWIDGET_LINE:
return LBackend_LineShow((struct LLine*)w);
case LWIDGET_SLIDER:
return LBackend_SliderShow((struct LSlider*)w);
case LWIDGET_TABLE:
return LBackend_TableShow((struct LTable*)w);
}
return NULL;
}
void LBackend_SetScreen(struct LScreen* s) {
for (int i = 0; i < s->numWidgets; i++)
{
struct LWidget* w = s->widgets[i];
UIView* view = ShowWidget(w);
[view_handle addSubview:view];
}
// TODO replace with native constraints some day, maybe
s->Layout(s);
}
void LBackend_CloseScreen(struct LScreen* s) {
if (!s) return;
// remove reference to soon to be garbage collected views
for (int i = 0; i < s->numWidgets; i++)
{
s->widgets[i]->meta = NULL;
}
// remove all widgets from previous screen
NSArray* elems = [view_handle subviews];
for (UIView* view in elems)
{
[view removeFromSuperview];
}
}
#endif

View file

@ -11,12 +11,8 @@
#include "Errors.h"
#include "Drawer2D.h"
#include "Launcher.h"
#include "LBackend.h"
#include "LWidgets.h"
#include "LScreens.h"
#include "Gui.h"
#include "LWeb.h"
#include "Funcs.h"
#include "Gui.h"
#include <mach-o/dyld.h>
#include <sys/stat.h>
#include <UIKit/UIKit.h>
@ -33,6 +29,17 @@
#define TEXT_ATTRIBUTE_COLOR UITextAttributeTextColor
#endif
// shared state with LBackend_ios.m
UITextField* kb_widget;
CGContextRef win_ctx;
UIView* view_handle;
UIColor* ToUIColor(BitmapCol color, float A);
NSString* ToNSString(const cc_string* text);
void LInput_SetKeyboardType(UITextField* fld, int flags);
void LInput_SetPlaceholder(UITextField* fld, const char* placeholder);
@interface CCWindow : UIWindow
@end
@ -45,7 +52,6 @@
static CCViewController* cc_controller;
static UIWindow* win_handle;
static UIView* view_handle;
static cc_bool launcherMode;
static void AddTouch(UITouch* t) {
@ -182,7 +188,6 @@ static void DeleteExportTempFile(void) {
}
static cc_bool kb_active;
static UITextField* kb_widget;
- (void)keyboardDidShow:(NSNotification*)notification {
NSDictionary* info = notification.userInfo;
if (kb_active) return;
@ -339,7 +344,7 @@ void Clipboard_SetText(const cc_string* value) { }
/*########################################################################################################################*
*------------------------------------------------------Common helpers--------------------------------------------------------*
*#########################################################################################################################*/
static UIColor* ToUIColor(BitmapCol color, float A) {
UIColor* ToUIColor(BitmapCol color, float A) {
// colorWithRed:green:blue:alpha - iOS 2.0
float R = BitmapCol_R(color) / 255.0f;
float G = BitmapCol_G(color) / 255.0f;
@ -347,72 +352,12 @@ static UIColor* ToUIColor(BitmapCol color, float A) {
return [UIColor colorWithRed:R green:G blue:B alpha:A];
}
static NSString* ToNSString(const cc_string* text) {
NSString* ToNSString(const cc_string* text) {
char raw[NATIVE_STR_LEN];
String_EncodeUtf8(raw, text);
return [NSString stringWithUTF8String:raw];
}
static NSMutableAttributedString* ToAttributedString(const cc_string* text) {
// NSMutableAttributedString - iOS 3.2
cc_string left = *text, part;
char colorCode = 'f';
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] init];
while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode))
{
BitmapCol color = Drawer2D_GetColor(colorCode);
NSString* bit = ToNSString(&part);
NSDictionary* attrs =
@{
//TEXT_ATTRIBUTE_FONT : font,
TEXT_ATTRIBUTE_COLOR : ToUIColor(color, 1.0f)
};
NSAttributedString* attr_bit = [[NSAttributedString alloc] initWithString:bit attributes:attrs];
[str appendAttributedString:attr_bit];
}
return str;
}
static UIColor* GetStringColor(const cc_string* text) {
cc_string left = *text, part;
char colorCode = 'f';
Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode);
BitmapCol color = Drawer2D_GetColor(colorCode);
return ToUIColor(color, 1.0f);
}
static NSString* GetColorlessString(const cc_string* text) {
char buffer[128];
cc_string tmp = String_FromArray(buffer);
String_AppendColorless(&tmp, text);
return ToNSString(&tmp);
}
static void FreeContents(void* info, const void* data, size_t size) { Mem_Free(data); }
// TODO probably a better way..
static UIImage* ToUIImage(struct Bitmap* bmp) {
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGDataProviderRef provider;
CGImageRef image;
provider = CGDataProviderCreateWithData(NULL, bmp->scan0,
Bitmap_DataSize(bmp->width, bmp->height), FreeContents);
image = CGImageCreate(bmp->width, bmp->height, 8, 32, bmp->width * 4, colorspace,
kCGBitmapByteOrder32Host | kCGImageAlphaNoneSkipFirst, provider, NULL, 0, 0);
UIImage* img = [UIImage imageWithCGImage:image];
CGImageRelease(image);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorspace);
return img;
}
/*########################################################################################################################*
*------------------------------------------------------Logging/Time-------------------------------------------------------*
@ -564,8 +509,6 @@ void ShowDialogCore(const char* title, const char* msg) {
}
@end
static void LInput_SetKeyboardType(UITextField* fld, int flags);
static void LInput_SetPlaceholder(UITextField* fld, const char* placeholder);
static UITextField* text_input;
static CCKBController* kb_controller;
@ -1224,612 +1167,22 @@ void interop_SysTextDraw(struct DrawTextArgs* args, struct Context2D* ctx, int x
#endif
/*########################################################################################################################*
*------------------------------------------------------UI Backend--------------------------------------------------------*
*#########################################################################################################################*/
static struct LWidget* FindWidgetForView(id obj) {
struct LScreen* s = Launcher_Active;
for (int i = 0; i < s->numWidgets; i++)
{
void* meta = s->widgets[i]->meta;
if (meta != (__bridge void*)obj) continue;
return s->widgets[i];
}
return NULL;
}
static void LTable_UpdateCellColor(UIView* view, struct ServerInfo* server, int row, cc_bool selected);
static void LTable_UpdateCell(UITableView* table, UITableViewCell* cell, int row);
static NSString* cellID = @"CC_Cell";
@interface CCUIController : NSObject<UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
@end
@implementation CCUIController
- (void)handleButtonPress:(id)sender {
struct LWidget* w = FindWidgetForView(sender);
if (!w) return;
struct LButton* btn = (struct LButton*)w;
btn->OnClick(btn);
}
- (void)handleTextChanged:(id)sender {
struct LWidget* w = FindWidgetForView(sender);
if (!w) return;
UITextField* src = (UITextField*)sender;
const char* str = src.text.UTF8String;
struct LInput* ipt = (struct LInput*)w;
ipt->text.length = 0;
String_AppendUtf8(&ipt->text, str, String_Length(str));
if (ipt->TextChanged) ipt->TextChanged(ipt);
}
- (void)handleValueChanged:(id)sender {
UISwitch* swt = (UISwitch*)sender;
UIView* parent = swt.superview;
struct LWidget* w = FindWidgetForView(parent);
if (!w) return;
struct LCheckbox* cb = (struct LCheckbox*)w;
cb->value = [swt isOn];
if (!cb->ValueChanged) return;
cb->ValueChanged(cb);
}
// === UITableViewDataSource ===
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// cellForRowAtIndexPath - iOS 2.0
//UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID];
}
LTable_UpdateCell(tableView, cell, (int)indexPath.row);
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// numberOfRowsInSection - iOS 2.0
struct LTable* w = (struct LTable*)FindWidgetForView(tableView);
return w ? w->rowsCount : 0;
}
// === UITableViewDelegate ===
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// didSelectRowAtIndexPath - iOS 2.0
int row = (int)indexPath.row;
struct ServerInfo* server = LTable_Get(row);
LTable_UpdateCellColor([tableView cellForRowAtIndexPath:indexPath], server, row, true);
struct LTable* w = (struct LTable*)FindWidgetForView(tableView);
if (!w) return;
LTable_RowClick(w, row);
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
// didDeselectRowAtIndexPath - iOS 2.0
int row = (int)indexPath.row;
struct ServerInfo* server = LTable_Get(row);
LTable_UpdateCellColor([tableView cellForRowAtIndexPath:indexPath], server, row, false);
}
// === UITextFieldDelegate ===
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
// textFieldShouldReturn - iOS 2.0
struct LWidget* w = FindWidgetForView(textField);
if (!w) return YES;
struct LWidget* sel = Launcher_Active->onEnterWidget;
if (sel && !w->skipsEnter) {
sel->OnClick(sel);
} else {
[textField resignFirstResponder];
}
return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
// textFieldDidBeginEditing - iOS 2.0
kb_widget = textField;
}
@end
static CCUIController* ui_controller;
void LBackend_Init(void) {
ui_controller = [[CCUIController alloc] init];
CFBridgingRetain(ui_controller); // prevent GC TODO even needed?
}
static CGContextRef win_ctx;
void LBackend_MarkDirty(void* widget) { }
void LBackend_Tick(void) { }
void LBackend_Free(void) { }
void LBackend_UpdateTitleFont(void) { }
static void DrawText(NSAttributedString* str, struct Context2D* ctx, int x, int y) {
// CTLineCreateWithAttributedString - iOS 3.2
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)str);
CGRect bounds = CTLineGetImageBounds(line, win_ctx);
int centreX = (int)(ctx->width / 2.0f - bounds.size.width / 2.0f);
CGContextSetTextPosition(win_ctx, centreX + x, ctx->height - y);
CTLineDraw(line, win_ctx);
}
void LBackend_DrawTitle(struct Context2D* ctx, const char* title) {
if (Launcher_BitmappedText()) {
struct FontDesc font;
Launcher_MakeTitleFont(&font);
Launcher_DrawTitle(&font, title, ctx);
// bitmapped fonts don't need to be freed
return;
}
// systemFontOfSize: - iOS 2.0
UIFont* font = [UIFont systemFontOfSize:42];
NSString* text = [NSString stringWithCString:title encoding:NSASCIIStringEncoding];
NSDictionary* attrs_bg =
@{
TEXT_ATTRIBUTE_FONT : font,
TEXT_ATTRIBUTE_COLOR : UIColor.blackColor
};
NSAttributedString* str_bg = [[NSAttributedString alloc] initWithString:text attributes:attrs_bg];
DrawText(str_bg, ctx, 4, 42);
NSDictionary* attrs_fg =
@{
TEXT_ATTRIBUTE_FONT : font,
TEXT_ATTRIBUTE_COLOR : UIColor.whiteColor
};
NSAttributedString* str_fg = [[NSAttributedString alloc] initWithString:text attributes:attrs_fg];
DrawText(str_fg, ctx, 0, 38);
}
void LBackend_InitFramebuffer(void) { }
void LBackend_FreeFramebuffer(void) { }
void LBackend_Redraw(void) {
struct Context2D ctx;
struct Bitmap bmp;
bmp.width = max(Window_Main.Width, 1);
bmp.height = max(Window_Main.Height, 1);
bmp.scan0 = (BitmapCol*)Mem_Alloc(bmp.width * bmp.height, BITMAPCOLOR_SIZE, "window pixels");
Context2D_Wrap(&ctx, &bmp);
win_ctx = CGBitmapContextCreate(bmp.scan0, bmp.width, bmp.height, 8, bmp.width * 4,
CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaNoneSkipFirst);
Launcher_Active->DrawBackground(Launcher_Active, &ctx);
view_handle.layer.contents = CFBridgingRelease(CGBitmapContextCreateImage(win_ctx));
Mem_Free(bmp.scan0); // TODO Context2D_Free
CGContextRelease(win_ctx);
}
static void LBackend_ButtonUpdateBackground(struct LButton* w);
void LBackend_ThemeChanged(void) {
struct LScreen* s = Launcher_Active;
LBackend_Redraw();
for (int i = 0; i < s->numWidgets; i++)
{
struct LWidget* w = s->widgets[i];
if (w->type != LWIDGET_BUTTON) continue;
LBackend_ButtonUpdateBackground((struct LButton*)w);
}
}
/*########################################################################################################################*
*------------------------------------------------------ButtonWidget-------------------------------------------------------*
*#########################################################################################################################*/
static void LBackend_ButtonUpdateBackground(struct LButton* w) {
UIButton* btn = (__bridge UIButton*)w->meta;
CGRect rect = [btn frame];
int width = (int)rect.size.width;
int height = (int)rect.size.height;
// memory freeing deferred until UIImage is freed (see FreeContents)
struct Bitmap bmp1, bmp2;
struct Context2D ctx1, ctx2;
Bitmap_Allocate(&bmp1, width, height);
Context2D_Wrap(&ctx1, &bmp1);
LButton_DrawBackground(&ctx1, 0, 0, width, height, false);
[btn setBackgroundImage:ToUIImage(&bmp1) forState:UIControlStateNormal];
Bitmap_Allocate(&bmp2, width, height);
Context2D_Wrap(&ctx2, &bmp2);
LButton_DrawBackground(&ctx2, 0, 0, width, height, true);
[btn setBackgroundImage:ToUIImage(&bmp2) forState:UIControlStateHighlighted];
}
void LBackend_ButtonInit(struct LButton* w, int width, int height) {
w->_textWidth = width;
w->_textHeight = height;
}
static UIView* LBackend_ButtonShow(struct LButton* w) {
UIButton* btn = [[UIButton alloc] init];
btn.frame = CGRectMake(0, 0, w->_textWidth, w->_textHeight);
[btn addTarget:ui_controller action:@selector(handleButtonPress:) forControlEvents:UIControlEventTouchUpInside];
w->meta = (__bridge void*)btn;
LBackend_ButtonUpdateBackground(w);
LBackend_ButtonUpdate(w);
return btn;
}
void LBackend_ButtonUpdate(struct LButton* w) {
UIButton* btn = (__bridge UIButton*)w->meta;
UIColor* color = GetStringColor(&w->text);
[btn setTitleColor:color forState:UIControlStateNormal];
NSString* str = GetColorlessString(&w->text);
[btn setTitle:str forState:UIControlStateNormal];
}
void LBackend_ButtonDraw(struct LButton* w) { }
/*########################################################################################################################*
*-----------------------------------------------------CheckboxWidget------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_CheckboxInit(struct LCheckbox* w) { }
static UIView* LBackend_CheckboxShow(struct LCheckbox* w) {
UIView* root = [[UIView alloc] init];
CGRect frame;
UISwitch* swt = [[UISwitch alloc] init];
[swt addTarget:ui_controller action:@selector(handleValueChanged:) forControlEvents:UIControlEventValueChanged];
UILabel* lbl = [[UILabel alloc] init];
lbl.backgroundColor = UIColor.clearColor;
lbl.textColor = UIColor.whiteColor;
lbl.text = ToNSString(&w->text);
[lbl sizeToFit]; // adjust label to fit text
[root addSubview:swt];
[root addSubview:lbl];
// label should be slightly to right of switch and vertically centred
frame = lbl.frame;
frame.origin.x = swt.frame.size.width + 10.0f;
frame.origin.y = swt.frame.size.height / 2 - frame.size.height / 2;
lbl.frame = frame;
// adjust root view height to enclose children
frame = root.frame;
frame.size.width = lbl.frame.origin.x + lbl.frame.size.width;
frame.size.height = max(swt.frame.size.height, lbl.frame.size.height);
root.frame = frame;
//root.userInteractionEnabled = YES;
w->meta = (__bridge void*)root;
LBackend_CheckboxUpdate(w);
return root;
}
void LBackend_CheckboxUpdate(struct LCheckbox* w) {
UIView* root = (__bridge UIView*)w->meta;
UISwitch* swt = (UISwitch*)root.subviews[0];
swt.on = w->value;
}
void LBackend_CheckboxDraw(struct LCheckbox* w) { }
/*########################################################################################################################*
*------------------------------------------------------InputWidget--------------------------------------------------------*
*#########################################################################################################################*/
static void LInput_SetKeyboardType(UITextField* fld, int flags) {
int type = flags & 0xFF;
if (type == KEYBOARD_TYPE_INTEGER) {
fld.keyboardType = UIKeyboardTypeNumberPad;
} else if (type == KEYBOARD_TYPE_PASSWORD) {
fld.secureTextEntry = YES;
}
if (flags & KEYBOARD_FLAG_SEND) {
fld.returnKeyType = UIReturnKeySend;
} else {
fld.returnKeyType = UIReturnKeyDone;
}
}
static void LInput_SetPlaceholder(UITextField* fld, const char* placeholder) {
if (!placeholder) return;
cc_string hint = String_FromReadonly(placeholder);
fld.placeholder = ToNSString(&hint);
}
void LBackend_InputInit(struct LInput* w, int width) {
w->_textHeight = width;
}
static UIView* LBackend_InputShow(struct LInput* w) {
UITextField* fld = [[UITextField alloc] init];
fld.frame = CGRectMake(0, 0, w->_textHeight, LINPUT_HEIGHT);
fld.borderStyle = UITextBorderStyleBezel;
fld.backgroundColor = UIColor.whiteColor;
fld.textColor = UIColor.blackColor;
fld.delegate = ui_controller;
[fld addTarget:ui_controller action:@selector(handleTextChanged:) forControlEvents:UIControlEventEditingChanged];
LInput_SetKeyboardType(fld, w->inputType);
LInput_SetPlaceholder(fld, w->hintText);
w->meta = (__bridge void*)fld;
LBackend_InputUpdate(w);
return fld;
}
void LBackend_InputUpdate(struct LInput* w) {
UITextField* fld = (__bridge UITextField*)w->meta;
fld.text = ToNSString(&w->text);
}
void LBackend_InputDraw(struct LInput* w) { }
void LBackend_InputTick(struct LInput* w) { }
void LBackend_InputSelect(struct LInput* w, int idx, cc_bool wasSelected) { }
void LBackend_InputUnselect(struct LInput* w) { }
/*########################################################################################################################*
*------------------------------------------------------LabelWidget--------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_LabelInit(struct LLabel* w) { }
static UIView* LBackend_LabelShow(struct LLabel* w) {
UILabel* lbl = [[UILabel alloc] init];
w->meta = (__bridge void*)lbl;
lbl.backgroundColor = UIColor.clearColor;
if (w->small) lbl.font = [UIFont systemFontOfSize:14.0f];
LBackend_LabelUpdate(w);
return lbl;
}
void LBackend_LabelUpdate(struct LLabel* w) {
UILabel* lbl = (__bridge UILabel*)w->meta;
if (!lbl) return;
if ([lbl respondsToSelector:@selector(attributedText)]) {
// attributedText - iOS 6.0
lbl.attributedText = ToAttributedString(&w->text);
} else {
lbl.textColor = GetStringColor(&w->text);
lbl.text = GetColorlessString(&w->text);
}
[lbl sizeToFit]; // adjust label to fit text
}
void LBackend_LabelDraw(struct LLabel* w) { }
/*########################################################################################################################*
*-------------------------------------------------------LineWidget--------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_LineInit(struct LLine* w, int width) {
w->_width = width;
}
static UIView* LBackend_LineShow(struct LLine* w) {
UIView* view = [[UIView alloc] init];
view.frame = CGRectMake(0, 0, w->_width, LLINE_HEIGHT);
w->meta = (__bridge void*)view;
BitmapCol color = LLine_GetColor();
view.backgroundColor = ToUIColor(color, 0.5f);
return view;
}
void LBackend_LineDraw(struct LLine* w) { }
/*########################################################################################################################*
*------------------------------------------------------SliderWidget-------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_SliderInit(struct LSlider* w, int width, int height) {
w->_width = width;
w->_height = height;
}
static UIView* LBackend_SliderShow(struct LSlider* w) {
UIProgressView* prg = [[UIProgressView alloc] init];
prg.frame = CGRectMake(0, 0, w->_width, w->_height);
prg.progressTintColor = ToUIColor(w->color, 1.0f);
w->meta = (__bridge void*)prg;
return prg;
}
void LBackend_SliderUpdate(struct LSlider* w) {
UIProgressView* prg = (__bridge UIProgressView*)w->meta;
prg.progress = w->value / 100.0f;
}
void LBackend_SliderDraw(struct LSlider* w) { }
/*########################################################################################################################*
*------------------------------------------------------TableWidget-------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_TableInit(struct LTable* w) { }
static UIView* LBackend_TableShow(struct LTable* w) {
UITableView* tbl = [[UITableView alloc] init];
tbl.delegate = ui_controller;
tbl.dataSource = ui_controller;
tbl.editing = NO;
tbl.allowsSelection = YES;
LTable_UpdateCellColor(tbl, NULL, 1, false);
//[tbl registerClass:UITableViewCell.class forCellReuseIdentifier:cellID];
w->meta = (__bridge void*)tbl;
return tbl;
}
void LBackend_TableUpdate(struct LTable* w) {
UITableView* tbl = (__bridge UITableView*)w->meta;
[tbl reloadData];
}
void LBackend_TableDraw(struct LTable* w) { }
void LBackend_TableReposition(struct LTable* w) { }
void LBackend_TableMouseDown(struct LTable* w, int idx) { }
void LBackend_TableMouseUp(struct LTable* w, int idx) { }
void LBackend_TableMouseMove(struct LTable* w, int idx) { }
static void LTable_UpdateCellColor(UIView* view, struct ServerInfo* server, int row, cc_bool selected) {
BitmapCol color = LTable_RowColor(row, selected, server && server->featured);
if (color) {
view.backgroundColor = ToUIColor(color, 1.0f);
view.opaque = YES;
} else {
view.backgroundColor = UIColor.clearColor;
view.opaque = NO;
}
}
static void LTable_UpdateCell(UITableView* table, UITableViewCell* cell, int row) {
struct ServerInfo* server = LTable_Get(row);
struct Flag* flag = Flags_Get(server);
char descBuffer[128];
cc_string desc = String_FromArray(descBuffer);
String_Format2(&desc, "%i/%i players, up for ", &server->players, &server->maxPlayers);
LTable_FormatUptime(&desc, server->uptime);
if (server->software.length) String_Format1(&desc, " | %s", &server->software);
if (flag && flag->meta)
cell.imageView.image = (__bridge UIImage*)flag->meta;
cell.textLabel.text = ToNSString(&server->name);
cell.detailTextLabel.text = ToNSString(&desc);//[ToNSString(&desc) stringByAppendingString:@"\nLine2"];
cell.textLabel.textColor = UIColor.whiteColor;
cell.detailTextLabel.textColor = UIColor.whiteColor;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
NSIndexPath* sel = table.indexPathForSelectedRow;
cc_bool selected = sel && sel.row == row;
LTable_UpdateCellColor(cell, server, row, selected);
}
// TODO only redraw flags
static void OnFlagsChanged(void) {
struct LScreen* s = Launcher_Active;
for (int i = 0; i < s->numWidgets; i++)
{
if (s->widgets[i]->type != LWIDGET_TABLE) continue;
UITableView* tbl = (__bridge UITableView*)s->widgets[i]->meta;
// trying to update cell.imageView.image doesn't seem to work,
// so pointlessly reload entire table data instead
NSIndexPath* selected = [tbl indexPathForSelectedRow];
[tbl reloadData];
[tbl selectRowAtIndexPath:selected animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
/*########################################################################################################################*
*------------------------------------------------------UI Backend--------------------------------------------------------*
*#########################################################################################################################*/
void LBackend_DecodeFlag(struct Flag* flag, cc_uint8* data, cc_uint32 len) {
NSData* ns_data = [NSData dataWithBytes:data length:len];
UIImage* img = [UIImage imageWithData:ns_data];
if (!img) return;
void Window_AllocFramebuffer(struct Bitmap* bmp, int width, int height) {
bmp->width = width;
bmp->height = height;
bmp->scan0 = (BitmapCol*)Mem_Alloc(width * height, BITMAPCOLOR_SIZE, "window pixels");
flag->meta = CFBridgingRetain(img);
OnFlagsChanged();
win_ctx = CGBitmapContextCreate(bmp->scan0, width, height, 8, width * 4,
CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaNoneSkipFirst);
}
static void LBackend_LayoutDimensions(struct LWidget* w, CGRect* r) {
const struct LLayout* l = w->layouts + 2;
while (l->type)
{
switch (l->type)
{
case LLAYOUT_WIDTH:
r->size.width = Window_Main.Width - (int)r->origin.x - Display_ScaleX(l->offset);
break;
case LLAYOUT_HEIGHT:
r->size.height = Window_Main.Height - (int)r->origin.y - Display_ScaleY(l->offset);
break;
}
l++;
}
void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) {
CGImageRef image = CGBitmapContextCreateImage(win_ctx);
view_handle.layer.contents = CFBridgingRelease(image);
}
void LBackend_LayoutWidget(struct LWidget* w) {
const struct LLayout* l = w->layouts;
UIView* view = (__bridge UIView*)w->meta;
CGRect r = [view frame];
int width = (int)r.size.width;
int height = (int)r.size.height;
r.origin.x = Gui_CalcPos(l[0].type & 0xFF, Display_ScaleX(l[0].offset), width, Window_Main.Width);
r.origin.y = Gui_CalcPos(l[1].type & 0xFF, Display_ScaleY(l[1].offset), height, Window_Main.Height);
// e.g. Table widget needs adjusts width/height based on window
if (l[1].type & LLAYOUT_EXTRA)
LBackend_LayoutDimensions(w, &r);
view.frame = r;
}
static UIView* ShowWidget(struct LWidget* w) {
switch (w->type)
{
case LWIDGET_BUTTON:
return LBackend_ButtonShow((struct LButton*)w);
case LWIDGET_CHECKBOX:
return LBackend_CheckboxShow((struct LCheckbox*)w);
case LWIDGET_INPUT:
return LBackend_InputShow((struct LInput*)w);
case LWIDGET_LABEL:
return LBackend_LabelShow((struct LLabel*)w);
case LWIDGET_LINE:
return LBackend_LineShow((struct LLine*)w);
case LWIDGET_SLIDER:
return LBackend_SliderShow((struct LSlider*)w);
case LWIDGET_TABLE:
return LBackend_TableShow((struct LTable*)w);
}
return NULL;
}
void LBackend_SetScreen(struct LScreen* s) {
for (int i = 0; i < s->numWidgets; i++)
{
struct LWidget* w = s->widgets[i];
UIView* view = ShowWidget(w);
[view_handle addSubview:view];
}
// TODO replace with native constraints some day, maybe
s->Layout(s);
}
void LBackend_CloseScreen(struct LScreen* s) {
if (!s) return;
// remove reference to soon to be garbage collected views
for (int i = 0; i < s->numWidgets; i++)
{
s->widgets[i]->meta = NULL;
}
// remove all widgets from previous screen
NSArray* elems = [view_handle subviews];
for (UIView* view in elems)
{
[view removeFromSuperview];
}
void Window_FreeFramebuffer(struct Bitmap* bmp) {
Mem_Free(bmp->scan0);
CGContextRelease(win_ctx);
}
#endif