diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp
index f6be2504..75af4a47 100755
--- a/higan/emulator/emulator.hpp
+++ b/higan/emulator/emulator.hpp
@@ -3,7 +3,7 @@
namespace Emulator {
static const char Name[] = "higan";
- static const char Version[] = "092.02";
+ static const char Version[] = "092.03";
static const char Author[] = "byuu";
static const char License[] = "GPLv3";
}
diff --git a/higan/nall/Makefile b/higan/nall/Makefile
index f422f49d..bc8ba566 100755
--- a/higan/nall/Makefile
+++ b/higan/nall/Makefile
@@ -38,7 +38,7 @@ ifeq ($(compiler),)
ifeq ($(platform),win)
compiler := g++
else ifeq ($(platform),osx)
- compiler := g++-mp-4.7
+ compiler := clang
else
compiler := g++-4.7
endif
diff --git a/higan/nall/beat/delta.hpp b/higan/nall/beat/delta.hpp
index ce120537..3fc400fa 100644
--- a/higan/nall/beat/delta.hpp
+++ b/higan/nall/beat/delta.hpp
@@ -97,7 +97,7 @@ bool bpsdelta::create(const string &filename, const string &metadata) {
for(unsigned n = 0; n < markupSize; n++) write(metadata[n]);
Node *sourceTree[65536], *targetTree[65536];
- for(unsigned n = 0; n < 65536; n++) sourceTree[n] = 0, targetTree[n] = 0;
+ for(unsigned n = 0; n < 65536; n++) sourceTree[n] = nullptr, targetTree[n] = nullptr;
//source tree creation
for(unsigned offset = 0; offset < sourceSize; offset++) {
diff --git a/higan/nall/compositor.hpp b/higan/nall/compositor.hpp
index 6b9245f6..c5aa8757 100755
--- a/higan/nall/compositor.hpp
+++ b/higan/nall/compositor.hpp
@@ -27,10 +27,10 @@ struct compositor {
bool compositor::enabled_metacity() {
FILE *fp = popen("gconftool-2 --get /apps/metacity/general/compositing_manager", "r");
- if(fp == 0) return false;
+ if(!fp) return false;
char buffer[512];
- if(fgets(buffer, sizeof buffer, fp) == 0) return false;
+ if(!fgets(buffer, sizeof buffer, fp)) return false;
if(!memcmp(buffer, "true", 4)) return true;
return false;
@@ -43,7 +43,7 @@ bool compositor::enable_metacity(bool status) {
} else {
fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager false", "r");
}
- if(fp == 0) return false;
+ if(!fp) return false;
pclose(fp);
return true;
}
@@ -52,10 +52,10 @@ bool compositor::enable_metacity(bool status) {
bool compositor::enabled_xfwm4() {
FILE *fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing'", "r");
- if(fp == 0) return false;
+ if(!fp) return false;
char buffer[512];
- if(fgets(buffer, sizeof buffer, fp) == 0) return false;
+ if(!fgets(buffer, sizeof buffer, fp)) return false;
if(!memcmp(buffer, "true", 4)) return true;
return false;
@@ -68,7 +68,7 @@ bool compositor::enable_xfwm4(bool status) {
} else {
fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing' -t 'bool' -s 'false'", "r");
}
- if(fp == 0) return false;
+ if(!fp) return false;
pclose(fp);
return true;
}
@@ -112,11 +112,11 @@ bool compositor::enable(bool status) {
bool compositor::enabled() {
HMODULE module = GetModuleHandleW(L"dwmapi");
- if(module == 0) module = LoadLibraryW(L"dwmapi");
- if(module == 0) return false;
+ if(module == nullptr) module = LoadLibraryW(L"dwmapi");
+ if(module == nullptr) return false;
auto pDwmIsCompositionEnabled = (HRESULT (WINAPI*)(BOOL*))GetProcAddress(module, "DwmIsCompositionEnabled");
- if(pDwmIsCompositionEnabled == 0) return false;
+ if(pDwmIsCompositionEnabled == nullptr) return false;
BOOL result;
if(pDwmIsCompositionEnabled(&result) != S_OK) return false;
@@ -125,11 +125,11 @@ bool compositor::enabled() {
bool compositor::enable(bool status) {
HMODULE module = GetModuleHandleW(L"dwmapi");
- if(module == 0) module = LoadLibraryW(L"dwmapi");
- if(module == 0) return false;
+ if(module == nullptr) module = LoadLibraryW(L"dwmapi");
+ if(module == nullptr) return false;
auto pDwmEnableComposition = (HRESULT (WINAPI*)(UINT))GetProcAddress(module, "DwmEnableComposition");
- if(pDwmEnableComposition == 0) return false;
+ if(pDwmEnableComposition == nullptr) return false;
if(pDwmEnableComposition(status) != S_OK) return false;
return true;
diff --git a/higan/nall/dl.hpp b/higan/nall/dl.hpp
index 3bd7d4d2..4732918c 100755
--- a/higan/nall/dl.hpp
+++ b/higan/nall/dl.hpp
@@ -17,7 +17,8 @@
namespace nall {
struct library {
- bool opened() const { return handle; }
+ explicit operator bool() const { return open(); }
+ bool open() const { return handle; }
bool open(const char*, const char* = "");
bool open_absolute(const char*);
void* sym(const char*);
@@ -48,7 +49,7 @@ namespace nall {
}
inline void* library::sym(const char *name) {
- if(!handle) return 0;
+ if(!handle) return nullptr;
return dlsym((void*)handle, name);
}
@@ -72,7 +73,7 @@ namespace nall {
}
inline void* library::sym(const char *name) {
- if(!handle) return 0;
+ if(!handle) return nullptr;
return dlsym((void*)handle, name);
}
@@ -96,7 +97,7 @@ namespace nall {
}
inline void* library::sym(const char *name) {
- if(!handle) return 0;
+ if(!handle) return nullptr;
return (void*)GetProcAddress((HMODULE)handle, name);
}
@@ -107,7 +108,8 @@ namespace nall {
}
#else
inline bool library::open(const char*, const char*) { return false; }
- inline void* library::sym(const char*) { return 0; }
+ inline bool library::open_absolute(const char*) { return false; }
+ inline void* library::sym(const char*) { return nullptr; }
inline void library::close() {}
#endif
};
diff --git a/higan/nall/dsp/buffer.hpp b/higan/nall/dsp/buffer.hpp
index 4386d0e9..19bc561d 100755
--- a/higan/nall/dsp/buffer.hpp
+++ b/higan/nall/dsp/buffer.hpp
@@ -1,16 +1,18 @@
#ifdef NALL_DSP_INTERNAL_HPP
struct Buffer {
- double **sample;
- uint16_t rdoffset;
- uint16_t wroffset;
- unsigned channels;
+ double **sample = nullptr;
+ uint16_t rdoffset = 0;
+ uint16_t wroffset = 0;
+ unsigned channels = 0;
void setChannels(unsigned channels) {
- for(unsigned c = 0; c < this->channels; c++) {
- if(sample[c]) delete[] sample[c];
+ if(sample) {
+ for(unsigned c = 0; c < this->channels; c++) {
+ if(sample[c]) delete[] sample[c];
+ }
+ delete[] sample;
}
- if(sample) delete[] sample;
this->channels = channels;
if(channels == 0) return;
@@ -40,7 +42,6 @@ struct Buffer {
}
Buffer() {
- channels = 0;
}
~Buffer() {
diff --git a/higan/nall/dsp/core.hpp b/higan/nall/dsp/core.hpp
index a5b967b1..353f77d3 100755
--- a/higan/nall/dsp/core.hpp
+++ b/higan/nall/dsp/core.hpp
@@ -69,7 +69,7 @@ protected:
real intensityInverse;
} settings;
- Resampler *resampler;
+ Resampler *resampler = nullptr;
inline void write(real channel[]);
#include "buffer.hpp"
diff --git a/higan/nall/dsp/resample/lib/sinc.hpp b/higan/nall/dsp/resample/lib/sinc.hpp
index 3e953679..67c793d6 100755
--- a/higan/nall/dsp/resample/lib/sinc.hpp
+++ b/higan/nall/dsp/resample/lib/sinc.hpp
@@ -584,7 +584,7 @@ void ResampleUtility::normalize(double* io, int size, double gain)
void* ResampleUtility::make_aligned(void* ptr, unsigned boundary)
{
- unsigned char* null_ptr = (unsigned char *)NULL;
+ unsigned char* null_ptr = (unsigned char *)nullptr;
unsigned char* uc_ptr = (unsigned char *)ptr;
uc_ptr += (boundary - ((uc_ptr - null_ptr) & (boundary - 1))) & (boundary - 1);
diff --git a/higan/nall/dsp/resample/sinc.hpp b/higan/nall/dsp/resample/sinc.hpp
index a77a1eeb..64e247eb 100755
--- a/higan/nall/dsp/resample/sinc.hpp
+++ b/higan/nall/dsp/resample/sinc.hpp
@@ -39,7 +39,7 @@ void ResampleSinc::sample() {
}
ResampleSinc::ResampleSinc(DSP &dsp) : Resampler(dsp) {
- for(unsigned n = 0; n < 8; n++) sinc_resampler[n] = 0;
+ for(unsigned n = 0; n < 8; n++) sinc_resampler[n] = nullptr;
}
void ResampleSinc::remakeSinc() {
diff --git a/higan/nall/file.hpp b/higan/nall/file.hpp
index 80b918a8..e51be466 100755
--- a/higan/nall/file.hpp
+++ b/higan/nall/file.hpp
@@ -264,6 +264,10 @@ namespace nall {
return fp;
}
+ explicit operator bool() const {
+ return open();
+ }
+
bool open(const string &filename, mode mode_) {
if(fp) return false;
@@ -293,17 +297,14 @@ namespace nall {
if(!fp) return;
buffer_flush();
fclose(fp);
- fp = 0;
+ fp = nullptr;
}
file() {
- memset(buffer, 0, sizeof buffer);
- buffer_offset = -1; //invalidate buffer
- buffer_dirty = false;
- fp = 0;
- file_offset = 0;
- file_size = 0;
- file_mode = mode::read;
+ }
+
+ file(const string &filename, mode mode_) {
+ open(filename, mode_);
}
~file() {
@@ -315,13 +316,13 @@ namespace nall {
private:
enum { buffer_size = 1 << 12, buffer_mask = buffer_size - 1 };
- char buffer[buffer_size];
- int buffer_offset;
- bool buffer_dirty;
- FILE *fp;
- unsigned file_offset;
- unsigned file_size;
- mode file_mode;
+ char buffer[buffer_size] = {0};
+ int buffer_offset = -1; //invalidate buffer
+ bool buffer_dirty = false;
+ FILE *fp = nullptr;
+ unsigned file_offset = 0;
+ unsigned file_size = 0;
+ mode file_mode = mode::read;
void buffer_sync() {
if(!fp) return; //file not open
diff --git a/higan/nall/filemap.hpp b/higan/nall/filemap.hpp
index f57d933c..f4875f24 100755
--- a/higan/nall/filemap.hpp
+++ b/higan/nall/filemap.hpp
@@ -22,14 +22,15 @@ namespace nall {
public:
enum class mode : unsigned { read, write, readwrite, writeread };
+ explicit operator bool() const { return open(); }
bool open() const { return p_open(); }
bool open(const char *filename, mode mode_) { return p_open(filename, mode_); }
void close() { return p_close(); }
unsigned size() const { return p_size; }
uint8_t* data() { return p_handle; }
const uint8_t* data() const { return p_handle; }
- filemap() : p_size(0), p_handle(0) { p_ctor(); }
- filemap(const char *filename, mode mode_) : p_size(0), p_handle(0) { p_ctor(); p_open(filename, mode_); }
+ filemap() : p_size(0), p_handle(nullptr) { p_ctor(); }
+ filemap(const char *filename, mode mode_) : p_size(0), p_handle(nullptr) { p_ctor(); p_open(filename, mode_); }
~filemap() { p_dtor(); }
private:
@@ -49,7 +50,7 @@ namespace nall {
bool p_open(const char *filename, mode mode_) {
if(file::exists(filename) && file::size(filename) == 0) {
- p_handle = 0;
+ p_handle = nullptr;
p_size = 0;
return true;
}
@@ -85,13 +86,13 @@ namespace nall {
break;
}
- p_filehandle = CreateFileW(utf16_t(filename), desired_access, FILE_SHARE_READ, NULL,
- creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL);
+ p_filehandle = CreateFileW(utf16_t(filename), desired_access, FILE_SHARE_READ, nullptr,
+ creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
if(p_filehandle == INVALID_HANDLE_VALUE) return false;
- p_size = GetFileSize(p_filehandle, NULL);
+ p_size = GetFileSize(p_filehandle, nullptr);
- p_maphandle = CreateFileMapping(p_filehandle, NULL, flprotect, 0, p_size, NULL);
+ p_maphandle = CreateFileMapping(p_filehandle, nullptr, flprotect, 0, p_size, nullptr);
if(p_maphandle == INVALID_HANDLE_VALUE) {
CloseHandle(p_filehandle);
p_filehandle = INVALID_HANDLE_VALUE;
@@ -105,7 +106,7 @@ namespace nall {
void p_close() {
if(p_handle) {
UnmapViewOfFile(p_handle);
- p_handle = 0;
+ p_handle = nullptr;
}
if(p_maphandle != INVALID_HANDLE_VALUE) {
@@ -141,7 +142,7 @@ namespace nall {
bool p_open(const char *filename, mode mode_) {
if(file::exists(filename) && file::size(filename) == 0) {
- p_handle = 0;
+ p_handle = nullptr;
p_size = 0;
return true;
}
@@ -175,9 +176,9 @@ namespace nall {
fstat(p_fd, &p_stat);
p_size = p_stat.st_size;
- p_handle = (uint8_t*)mmap(0, p_size, mmap_flags, MAP_SHARED, p_fd, 0);
+ p_handle = (uint8_t*)mmap(nullptr, p_size, mmap_flags, MAP_SHARED, p_fd, 0);
if(p_handle == MAP_FAILED) {
- p_handle = 0;
+ p_handle = nullptr;
::close(p_fd);
p_fd = -1;
return false;
@@ -189,7 +190,7 @@ namespace nall {
void p_close() {
if(p_handle) {
munmap(p_handle, p_size);
- p_handle = 0;
+ p_handle = nullptr;
}
if(p_fd >= 0) {
diff --git a/higan/nall/function.hpp b/higan/nall/function.hpp
index ca574b8c..3fbb337a 100755
--- a/higan/nall/function.hpp
+++ b/higan/nall/function.hpp
@@ -34,7 +34,7 @@ namespace nall {
};
public:
- operator bool() const { return callback; }
+ explicit operator bool() const { return callback; }
R operator()(P... p) const { return (*callback)(std::forward
(p)...); }
void reset() { if(callback) { delete callback; callback = nullptr; } }
diff --git a/higan/nall/http.hpp b/higan/nall/http.hpp
index 48aeb097..f7700cea 100755
--- a/higan/nall/http.hpp
+++ b/higan/nall/http.hpp
@@ -24,7 +24,7 @@ struct http {
string header;
inline void download(const string &path, uint8_t *&data, unsigned &size) {
- data = 0;
+ data = nullptr;
size = 0;
send({
@@ -147,7 +147,7 @@ struct http {
inline void disconnect() {
close(serversocket);
freeaddrinfo(serverinfo);
- serverinfo = 0;
+ serverinfo = nullptr;
serversocket = -1;
}
diff --git a/higan/nall/inflate.hpp b/higan/nall/inflate.hpp
index cbbf6d29..8e75ce76 100755
--- a/higan/nall/inflate.hpp
+++ b/higan/nall/inflate.hpp
@@ -89,7 +89,7 @@ inline int stored(state *s) {
) return 2;
if(s->incnt + len > s->inlen) return 2;
- if(s->out != 0) {
+ if(s->out != nullptr) {
if(s->outcnt + len > s->outlen) return 1;
while(len--) s->out[s->outcnt++] = s->in[s->incnt++];
} else {
@@ -186,7 +186,7 @@ inline int codes(state *s, huffman *lencode, huffman *distcode) {
symbol = decode(s, lencode);
if(symbol < 0) return symbol;
if(symbol < 256) {
- if(s->out != 0) {
+ if(s->out != nullptr) {
if(s->outcnt == s->outlen) return 1;
s->out[s->outcnt] = symbol;
}
@@ -203,7 +203,7 @@ inline int codes(state *s, huffman *lencode, huffman *distcode) {
if(dist > s->outcnt) return -11;
#endif
- if(s->out != 0) {
+ if(s->out != nullptr) {
if(s->outcnt + len > s->outlen) return 1;
while(len--) {
s->out[s->outcnt] =
diff --git a/higan/nall/lzss.hpp b/higan/nall/lzss.hpp
index fb3e0ba6..d595f911 100755
--- a/higan/nall/lzss.hpp
+++ b/higan/nall/lzss.hpp
@@ -61,7 +61,7 @@ bool lzss::compress(const string &filename) {
if(targetFile.open(filename, file::mode::write) == false) return false;
for(unsigned n = 0; n < 32; n += 8) targetFile.write(sourceSize >> n);
- for(unsigned n = 0; n < 65536; n++) tree[n] = 0;
+ for(unsigned n = 0; n < 65536; n++) tree[n] = nullptr;
uint8_t buffer[25];
unsigned sourceOffset = 0;
@@ -81,7 +81,7 @@ bool lzss::compress(const string &filename) {
while(node) {
if(node->offset < sourceOffset - 0x80000) {
//out-of-range: all subsequent nodes will also be, so free up their memory
- if(node->next) { delete node->next; node->next = 0; }
+ if(node->next) { delete node->next; node->next = nullptr; }
break;
}
diff --git a/higan/nall/png.hpp b/higan/nall/png.hpp
index f5ebaab4..ef864228 100755
--- a/higan/nall/png.hpp
+++ b/higan/nall/png.hpp
@@ -69,7 +69,7 @@ bool png::decode(const uint8_t *sourceData, unsigned sourceSize) {
if(read(sourceData + 0, 4) != 0x89504e47) return false;
if(read(sourceData + 4, 4) != 0x0d0a1a0a) return false;
- uint8_t *compressedData = 0;
+ uint8_t *compressedData = nullptr;
unsigned compressedSize = 0;
unsigned offset = 8;
@@ -150,7 +150,7 @@ bool png::decode(const uint8_t *sourceData, unsigned sourceSize) {
if(filter(data, interlacedData, info.width, info.height) == false) {
delete[] interlacedData;
delete[] data;
- data = 0;
+ data = nullptr;
return false;
}
} else {
@@ -159,7 +159,7 @@ bool png::decode(const uint8_t *sourceData, unsigned sourceSize) {
if(deinterlace(passData, pass) == false) {
delete[] interlacedData;
delete[] data;
- data = 0;
+ data = nullptr;
return false;
}
}
@@ -172,13 +172,13 @@ bool png::decode(const uint8_t *sourceData, unsigned sourceSize) {
unsigned png::interlace(unsigned pass, unsigned index) {
static const unsigned data[7][4] = {
//x-distance, y-distance, x-origin, y-origin
- { 8, 8, 0, 0 },
- { 8, 8, 4, 0 },
- { 4, 8, 0, 4 },
- { 4, 4, 2, 0 },
- { 2, 4, 0, 2 },
- { 2, 2, 1, 0 },
- { 1, 2, 0, 1 },
+ {8, 8, 0, 0},
+ {8, 8, 4, 0},
+ {4, 8, 0, 4},
+ {4, 4, 2, 0},
+ {2, 4, 0, 2},
+ {2, 2, 1, 0},
+ {1, 2, 0, 1},
};
return data[pass][index];
}
diff --git a/higan/nall/serializer.hpp b/higan/nall/serializer.hpp
index fcb39456..a616cbdf 100755
--- a/higan/nall/serializer.hpp
+++ b/higan/nall/serializer.hpp
@@ -86,7 +86,7 @@ namespace nall {
return *this;
}
- serializer(const serializer &s) : idata(0) {
+ serializer(const serializer &s) : idata(nullptr) {
operator=(s);
}
@@ -99,7 +99,7 @@ namespace nall {
isize = s.isize;
icapacity = s.icapacity;
- s.idata = 0;
+ s.idata = nullptr;
return *this;
}
@@ -110,7 +110,7 @@ namespace nall {
//construction
serializer() {
imode = Size;
- idata = 0;
+ idata = nullptr;
isize = 0;
icapacity = 0;
}
diff --git a/higan/nall/string/base.hpp b/higan/nall/string/base.hpp
index d5f85ca4..7a1e55fb 100755
--- a/higan/nall/string/base.hpp
+++ b/higan/nall/string/base.hpp
@@ -22,6 +22,7 @@ namespace nall {
};
struct string {
+ //deprecated: use string text = file::read(filename);
inline static string read(const string &filename);
inline static string date();
@@ -71,7 +72,7 @@ namespace nall {
template inline string& ltrim(const char *key = " ");
template inline string& rtrim(const char *key = " ");
- template inline string& trim(const char *key = " ", const char *rkey = 0);
+ template inline string& trim(const char *key = " ", const char *rkey = nullptr);
inline string& strip();
inline optional position(const char *key) const;
@@ -79,6 +80,7 @@ namespace nall {
inline optional qposition(const char *key) const;
inline optional iqposition(const char *key) const;
+ inline explicit operator bool() const;
inline operator const char*() const;
inline char* operator()();
inline char& operator[](int);
@@ -197,7 +199,7 @@ namespace nall {
//trim.hpp
template inline char* ltrim(char *str, const char *key = " ");
template inline char* rtrim(char *str, const char *key = " ");
- template inline char* trim(char *str, const char *key = " ", const char *rkey = 0);
+ template inline char* trim(char *str, const char *key = " ", const char *rkey = nullptr);
inline char* strip(char *s);
//utility.hpp
diff --git a/higan/nall/string/cast.hpp b/higan/nall/string/cast.hpp
index 7c7e276b..eee42177 100755
--- a/higan/nall/string/cast.hpp
+++ b/higan/nall/string/cast.hpp
@@ -116,6 +116,32 @@ template<> struct stringify {
stringify(long double value) { fp(data, value); }
};
+// arrays
+
+template<> struct stringify> {
+ char *text;
+ operator const char*() const { return text; }
+ stringify(vector value) {
+ text = new char[value.size() + 1]();
+ memcpy(text, value.data(), value.size());
+ }
+ ~stringify() {
+ delete[] text;
+ }
+};
+
+template<> struct stringify&> {
+ char *text;
+ operator const char*() const { return text; }
+ stringify(const vector &value) {
+ text = new char[value.size() + 1]();
+ memcpy(text, value.data(), value.size());
+ }
+ ~stringify() {
+ delete[] text;
+ }
+};
+
// strings
template<> struct stringify {
diff --git a/higan/nall/string/convert.hpp b/higan/nall/string/convert.hpp
index f5a2a780..27448770 100755
--- a/higan/nall/string/convert.hpp
+++ b/higan/nall/string/convert.hpp
@@ -3,7 +3,7 @@
namespace nall {
char* strlower(char *str) {
- if(!str) return 0;
+ if(!str) return nullptr;
int i = 0;
while(str[i]) {
str[i] = chrlower(str[i]);
@@ -13,7 +13,7 @@ char* strlower(char *str) {
}
char* strupper(char *str) {
- if(!str) return 0;
+ if(!str) return nullptr;
int i = 0;
while(str[i]) {
str[i] = chrupper(str[i]);
@@ -23,7 +23,7 @@ char* strupper(char *str) {
}
char* qstrlower(char *s) {
- if(!s) return 0;
+ if(!s) return nullptr;
bool quoted = false;
while(*s) {
if(*s == '\"' || *s == '\'') quoted ^= 1;
@@ -33,7 +33,7 @@ char* qstrlower(char *s) {
}
char* qstrupper(char *s) {
- if(!s) return 0;
+ if(!s) return nullptr;
bool quoted = false;
while(*s) {
if(*s == '\"' || *s == '\'') quoted ^= 1;
diff --git a/higan/nall/string/core.hpp b/higan/nall/string/core.hpp
index 64c9250d..043c575b 100755
--- a/higan/nall/string/core.hpp
+++ b/higan/nall/string/core.hpp
@@ -55,6 +55,10 @@ string& string::append_(const char *s) {
return *this;
}
+string::operator bool() const {
+ return !empty();
+}
+
string::operator const char*() const {
return data;
}
diff --git a/higan/nall/string/datetime.hpp b/higan/nall/string/datetime.hpp
index 5382fdfd..438631bf 100644
--- a/higan/nall/string/datetime.hpp
+++ b/higan/nall/string/datetime.hpp
@@ -3,7 +3,7 @@
namespace nall {
string string::date() {
- time_t timestamp = ::time(0);
+ time_t timestamp = ::time(nullptr);
tm *info = localtime(×tamp);
return {
decimal<4, '0'>(1900 + info->tm_year), "-",
@@ -13,7 +13,7 @@ string string::date() {
}
string string::time() {
- time_t timestamp = ::time(0);
+ time_t timestamp = ::time(nullptr);
tm *info = localtime(×tamp);
return {
decimal<2, '0'>(info->tm_hour), ":",
diff --git a/higan/nall/string/markup/bml.hpp b/higan/nall/string/markup/bml.hpp
index 338ca406..26bf6685 100644
--- a/higan/nall/string/markup/bml.hpp
+++ b/higan/nall/string/markup/bml.hpp
@@ -80,7 +80,7 @@ protected:
parseName(p);
parseData(p);
parseAttributes(p);
- if(*p++ != '\n') throw "Missing line feed";
+ if(*p && *p++ != '\n') throw "Missing line feed";
while(*p) {
if(*p == '\n') { p++; continue; }
diff --git a/higan/nall/string/platform.hpp b/higan/nall/string/platform.hpp
index 90b6d6b8..0129f4d2 100755
--- a/higan/nall/string/platform.hpp
+++ b/higan/nall/string/platform.hpp
@@ -39,7 +39,7 @@ string userpath() {
string result;
#ifdef _WIN32
wchar_t path[PATH_MAX] = L"";
- SHGetFolderPathW(0, CSIDL_PROFILE | CSIDL_FLAG_CREATE, 0, 0, path);
+ SHGetFolderPathW(nullptr, CSIDL_PROFILE | CSIDL_FLAG_CREATE, nullptr, 0, path);
result = (const char*)utf8_t(path);
result.transform("\\", "/");
#else
@@ -59,7 +59,7 @@ string configpath() {
string result;
#ifdef _WIN32
wchar_t path[PATH_MAX] = L"";
- SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, path);
+ SHGetFolderPathW(nullptr, CSIDL_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path);
result = (const char*)utf8_t(path);
result.transform("\\", "/");
#else
diff --git a/higan/nall/string/utility.hpp b/higan/nall/string/utility.hpp
index 657383f8..2379e7fb 100755
--- a/higan/nall/string/utility.hpp
+++ b/higan/nall/string/utility.hpp
@@ -232,7 +232,7 @@ unsigned fp(char *str, long double value) {
string fp(long double value) {
string temp;
- temp.reserve(fp(0, value));
+ temp.reserve(fp(nullptr, value));
fp(temp(), value);
return temp;
}
diff --git a/higan/nall/string/wildcard.hpp b/higan/nall/string/wildcard.hpp
index 9d2359d5..ea11490a 100755
--- a/higan/nall/string/wildcard.hpp
+++ b/higan/nall/string/wildcard.hpp
@@ -3,7 +3,7 @@
namespace nall {
bool wildcard(const char *s, const char *p) {
- const char *cp = 0, *mp = 0;
+ const char *cp = nullptr, *mp = nullptr;
while(*s && *p != '*') {
if(*p != '?' && *s != *p) return false;
p++, s++;
@@ -23,7 +23,7 @@ bool wildcard(const char *s, const char *p) {
}
bool iwildcard(const char *s, const char *p) {
- const char *cp = 0, *mp = 0;
+ const char *cp = nullptr, *mp = nullptr;
while(*s && *p != '*') {
if(*p != '?' && chrlower(*s) != chrlower(*p)) return false;
p++, s++;
diff --git a/higan/nall/thread.hpp b/higan/nall/thread.hpp
index de894f2d..2ac730a0 100644
--- a/higan/nall/thread.hpp
+++ b/higan/nall/thread.hpp
@@ -14,7 +14,7 @@ namespace nall {
struct thread {
thread(function entryPoint) : entryPoint(entryPoint), completed(false), dead(false) {
initialize();
- pthread_create(&pthread, NULL, thread_entry_point, (void*)this);
+ pthread_create(&pthread, nullptr, thread_entry_point, (void*)this);
}
~thread() {
@@ -28,7 +28,7 @@ namespace nall {
void join() {
if(dead) return;
dead = true;
- pthread_join(pthread, NULL);
+ pthread_join(pthread, nullptr);
}
static bool primary() {
@@ -59,7 +59,7 @@ namespace nall {
thread *context = (thread*)parameter;
context->entryPoint();
context->completed = true;
- pthread_exit(0);
+ pthread_exit(nullptr);
}
}
#elif defined(PLATFORM_WIN)
@@ -69,7 +69,7 @@ namespace nall {
struct thread {
thread(function entryPoint) : entryPoint(entryPoint), completed(false), dead(false) {
initialize();
- hthread = CreateThread(NULL, 0, thread_entry_point, (void*)this, 0, NULL);
+ hthread = CreateThread(nullptr, 0, thread_entry_point, (void*)this, 0, nullptr);
}
~thread() {
diff --git a/higan/nall/vector.hpp b/higan/nall/vector.hpp
index 6818c69d..ea714b7b 100755
--- a/higan/nall/vector.hpp
+++ b/higan/nall/vector.hpp
@@ -21,7 +21,7 @@ namespace nall {
unsigned objectsize;
public:
- operator bool() const { return pool; }
+ explicit operator bool() const { return pool; }
T* data() { return pool; }
const T* data() const { return pool; }
diff --git a/higan/nall/windows/guid.hpp b/higan/nall/windows/guid.hpp
index 386cfc75..adee891d 100755
--- a/higan/nall/windows/guid.hpp
+++ b/higan/nall/windows/guid.hpp
@@ -9,7 +9,7 @@ namespace nall {
//generate unique GUID
inline string guid() {
random_lfsr lfsr;
- lfsr.seed(time(0));
+ lfsr.seed(time(nullptr));
for(unsigned n = 0; n < 256; n++) lfsr();
string output;
diff --git a/higan/nall/windows/registry.hpp b/higan/nall/windows/registry.hpp
index 0774e04a..cb035e19 100755
--- a/higan/nall/windows/registry.hpp
+++ b/higan/nall/windows/registry.hpp
@@ -31,7 +31,7 @@ struct registry {
if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) {
wchar_t data[NWR_SIZE] = L"";
DWORD size = NWR_SIZE * sizeof(wchar_t);
- LONG result = RegQueryValueExW(handle, utf16_t(node), NULL, NULL, (LPBYTE)&data, (LPDWORD)&size);
+ LONG result = RegQueryValueExW(handle, utf16_t(node), nullptr, nullptr, (LPBYTE)&data, (LPDWORD)&size);
RegCloseKey(handle);
if(result == ERROR_SUCCESS) return true;
}
@@ -46,7 +46,7 @@ struct registry {
if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) {
wchar_t data[NWR_SIZE] = L"";
DWORD size = NWR_SIZE * sizeof(wchar_t);
- LONG result = RegQueryValueExW(handle, utf16_t(node), NULL, NULL, (LPBYTE)&data, (LPDWORD)&size);
+ LONG result = RegQueryValueExW(handle, utf16_t(node), nullptr, nullptr, (LPBYTE)&data, (LPDWORD)&size);
RegCloseKey(handle);
if(result == ERROR_SUCCESS) return (const char*)utf8_t(data);
}
@@ -60,7 +60,7 @@ struct registry {
DWORD disposition;
for(unsigned n = 0; n < part.size(); n++) {
path.append(part[n]);
- if(RegCreateKeyExW(rootKey, utf16_t(path), 0, NULL, 0, NWR_FLAGS | KEY_ALL_ACCESS, NULL, &handle, &disposition) == ERROR_SUCCESS) {
+ if(RegCreateKeyExW(rootKey, utf16_t(path), 0, nullptr, 0, NWR_FLAGS | KEY_ALL_ACCESS, nullptr, &handle, &disposition) == ERROR_SUCCESS) {
if(n == part.size() - 1) {
RegSetValueExW(handle, utf16_t(node), 0, REG_SZ, (BYTE*)(wchar_t*)utf16_t(data), (data.length() + 1) * sizeof(wchar_t));
}
@@ -86,17 +86,17 @@ struct registry {
string path = part.concatenate("\\");
if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) {
DWORD folders, nodes;
- RegQueryInfoKey(handle, NULL, NULL, NULL, &folders, NULL, NULL, &nodes, NULL, NULL, NULL, NULL);
+ RegQueryInfoKey(handle, nullptr, nullptr, nullptr, &folders, nullptr, nullptr, &nodes, nullptr, nullptr, nullptr, nullptr);
for(unsigned n = 0; n < folders; n++) {
wchar_t name[NWR_SIZE] = L"";
DWORD size = NWR_SIZE * sizeof(wchar_t);
- RegEnumKeyEx(handle, n, (wchar_t*)&name, &size, NULL, NULL, NULL, NULL);
+ RegEnumKeyEx(handle, n, (wchar_t*)&name, &size, nullptr, nullptr, nullptr, nullptr);
result.append({(const char*)utf8_t(name), "/"});
}
for(unsigned n = 0; n < nodes; n++) {
wchar_t name[NWR_SIZE] = L"";
DWORD size = NWR_SIZE * sizeof(wchar_t);
- RegEnumValueW(handle, n, (wchar_t*)&name, &size, NULL, NULL, NULL, NULL);
+ RegEnumValueW(handle, n, (wchar_t*)&name, &size, nullptr, nullptr, nullptr, nullptr);
result.append((const char*)utf8_t(name));
}
RegCloseKey(handle);
@@ -111,7 +111,7 @@ private:
if(name == "HKCU") return HKEY_CURRENT_USER;
if(name == "HKLM") return HKEY_LOCAL_MACHINE;
if(name == "HKU" ) return HKEY_USERS;
- return NULL;
+ return nullptr;
}
};
diff --git a/higan/nall/windows/utf8.hpp b/higan/nall/windows/utf8.hpp
index b1374943..05f89660 100755
--- a/higan/nall/windows/utf8.hpp
+++ b/higan/nall/windows/utf8.hpp
@@ -30,7 +30,7 @@ namespace nall {
utf16_t(const char *s = "") {
if(!s) s = "";
- unsigned length = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0);
+ unsigned length = MultiByteToWideChar(CP_UTF8, 0, s, -1, nullptr, 0);
buffer = new wchar_t[length + 1]();
MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length);
}
@@ -56,9 +56,9 @@ namespace nall {
utf8_t(const wchar_t *s = L"") {
if(!s) s = L"";
- unsigned length = WideCharToMultiByte(CP_UTF8, 0, s, -1, 0, 0, (const char*)0, (BOOL*)0);
+ unsigned length = WideCharToMultiByte(CP_UTF8, 0, s, -1, nullptr, 0, nullptr, nullptr);
buffer = new char[length + 1]();
- WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, (const char*)0, (BOOL*)0);
+ WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, nullptr, nullptr);
}
~utf8_t() {
diff --git a/higan/nall/zip.hpp b/higan/nall/zip.hpp
index 0a73ccdd..2cc673ce 100755
--- a/higan/nall/zip.hpp
+++ b/higan/nall/zip.hpp
@@ -11,7 +11,7 @@ namespace nall {
struct zip {
zip(const string &filename) {
fp.open(filename, file::mode::write);
- time_t currentTime = time(0);
+ time_t currentTime = time(nullptr);
tm *info = localtime(¤tTime);
dosTime = (info->tm_hour << 11) | (info->tm_min << 5) | (info->tm_sec >> 1);
dosDate = ((info->tm_year - 80) << 9) | ((1 + info->tm_mon) << 5) + (info->tm_mday);
diff --git a/higan/phoenix/cocoa/action/action.cpp b/higan/phoenix/cocoa/action/action.cpp
new file mode 100644
index 00000000..41578414
--- /dev/null
+++ b/higan/phoenix/cocoa/action/action.cpp
@@ -0,0 +1,21 @@
+namespace phoenix {
+
+void pAction::setEnabled(bool enabled) {
+ @autoreleasepool {
+ [cocoaAction setEnabled:enabled];
+ }
+}
+
+void pAction::setVisible(bool visible) {
+ @autoreleasepool {
+ [cocoaAction setHidden:!visible];
+ }
+}
+
+void pAction::constructor() {
+}
+
+void pAction::destructor() {
+}
+
+}
diff --git a/higan/phoenix/cocoa/action/action.hpp b/higan/phoenix/cocoa/action/action.hpp
new file mode 100644
index 00000000..f8a7325c
--- /dev/null
+++ b/higan/phoenix/cocoa/action/action.hpp
@@ -0,0 +1,15 @@
+namespace phoenix {
+
+struct pAction : public pObject {
+ Action &action;
+ NSMenuItem *cocoaAction;
+
+ void setEnabled(bool enabled);
+ void setVisible(bool visible);
+
+ pAction(Action &action) : pObject(action), action(action) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/action/check-item.cpp b/higan/phoenix/cocoa/action/check-item.cpp
new file mode 100644
index 00000000..57c2ef0f
--- /dev/null
+++ b/higan/phoenix/cocoa/action/check-item.cpp
@@ -0,0 +1,55 @@
+@implementation CocoaCheckItem : NSMenuItem
+
+-(id) initWith :(phoenix::CheckItem&)checkItemReference {
+ if(self = [super initWithTitle:@"" action:@selector(activate) keyEquivalent:@""]) {
+ checkItem = &checkItemReference;
+
+ [self setTarget:self];
+ }
+ return self;
+}
+
+-(void) activate {
+ checkItem->state.checked = !checkItem->state.checked;
+ auto state = checkItem->state.checked ? NSOnState : NSOffState;
+ [self setState:state];
+ if(checkItem->onToggle) checkItem->onToggle();
+}
+
+@end
+
+namespace phoenix {
+
+bool pCheckItem::checked() {
+ @autoreleasepool {
+ return [cocoaAction state] != NSOffState;
+ }
+}
+
+void pCheckItem::setChecked(bool checked) {
+ @autoreleasepool {
+ auto state = checked ? NSOnState : NSOffState;
+ [cocoaAction setState:state];
+ }
+}
+
+void pCheckItem::setText(const string &text) {
+ @autoreleasepool {
+ [cocoaAction setTitle:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pCheckItem::constructor() {
+ @autoreleasepool {
+ cocoaAction = cocoaCheckItem = [[CocoaCheckItem alloc] initWith:checkItem];
+ setText(checkItem.state.text);
+ }
+}
+
+void pCheckItem::destructor() {
+ @autoreleasepool {
+ [cocoaAction release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/action/check-item.hpp b/higan/phoenix/cocoa/action/check-item.hpp
new file mode 100644
index 00000000..b3db94e5
--- /dev/null
+++ b/higan/phoenix/cocoa/action/check-item.hpp
@@ -0,0 +1,24 @@
+@interface CocoaCheckItem : NSMenuItem {
+@public
+ phoenix::CheckItem *checkItem;
+}
+-(id) initWith :(phoenix::CheckItem&)checkItem;
+-(void) activate;
+@end
+
+namespace phoenix {
+
+struct pCheckItem : public pAction {
+ CheckItem &checkItem;
+ CocoaCheckItem *cocoaCheckItem;
+
+ bool checked();
+ void setChecked(bool checked);
+ void setText(const string &text);
+
+ pCheckItem(CheckItem &checkItem) : pAction(checkItem), checkItem(checkItem) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/action/item.cpp b/higan/phoenix/cocoa/action/item.cpp
new file mode 100644
index 00000000..5ceea8ea
--- /dev/null
+++ b/higan/phoenix/cocoa/action/item.cpp
@@ -0,0 +1,45 @@
+@implementation CocoaItem : NSMenuItem
+
+-(id) initWith :(phoenix::Item&)itemReference {
+ if(self = [super initWithTitle:@"" action:@selector(activate) keyEquivalent:@""]) {
+ item = &itemReference;
+
+ [self setTarget:self];
+ }
+ return self;
+}
+
+-(void) activate {
+ if(item->onActivate) item->onActivate();
+}
+
+@end
+
+namespace phoenix {
+
+void pItem::setImage(const image &image) {
+ @autoreleasepool {
+ unsigned size = 15; //there is no API to retrieve the optimal size
+ [cocoaAction setImage:NSMakeImage(image, size, size)];
+ }
+}
+
+void pItem::setText(const string &text) {
+ @autoreleasepool {
+ [cocoaAction setTitle:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pItem::constructor() {
+ @autoreleasepool {
+ cocoaAction = cocoaItem = [[CocoaItem alloc] initWith:item];
+ }
+}
+
+void pItem::destructor() {
+ @autoreleasepool {
+ [cocoaAction release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/action/item.hpp b/higan/phoenix/cocoa/action/item.hpp
new file mode 100644
index 00000000..44ca7727
--- /dev/null
+++ b/higan/phoenix/cocoa/action/item.hpp
@@ -0,0 +1,23 @@
+@interface CocoaItem : NSMenuItem {
+@public
+ phoenix::Item *item;
+}
+-(id) initWith :(phoenix::Item&)item;
+-(void) activate;
+@end
+
+namespace phoenix {
+
+struct pItem : public pAction {
+ Item &item;
+ CocoaItem *cocoaItem;
+
+ void setImage(const image &image);
+ void setText(const string &text);
+
+ pItem(Item &item) : pAction(item), item(item) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/action/menu.cpp b/higan/phoenix/cocoa/action/menu.cpp
new file mode 100644
index 00000000..3ce66289
--- /dev/null
+++ b/higan/phoenix/cocoa/action/menu.cpp
@@ -0,0 +1,61 @@
+@implementation CocoaMenu : NSMenuItem
+
+-(id) initWith :(phoenix::Menu&)menuReference {
+ if(self = [super initWithTitle:@"" action:nil keyEquivalent:@""]) {
+ menu = &menuReference;
+
+ cocoaMenu = [[NSMenu alloc] initWithTitle:@""];
+ [self setSubmenu:cocoaMenu];
+ }
+ return self;
+}
+
+-(NSMenu*) cocoaMenu {
+ return cocoaMenu;
+}
+
+@end
+
+namespace phoenix {
+
+void pMenu::append(Action &action) {
+ @autoreleasepool {
+ [[cocoaAction cocoaMenu] addItem:action.p.cocoaAction];
+ }
+}
+
+void pMenu::remove(Action &action) {
+ @autoreleasepool {
+ [[cocoaAction cocoaMenu] removeItem:action.p.cocoaAction];
+ }
+}
+
+void pMenu::setImage(const image &image) {
+ @autoreleasepool {
+ unsigned size = 15; //there is no API to retrieve the optimal size
+ [cocoaAction setImage:NSMakeImage(image, size, size)];
+ }
+}
+
+void pMenu::setText(const string &text) {
+ @autoreleasepool {
+ [[cocoaAction cocoaMenu] setTitle:[NSString stringWithUTF8String:text]];
+ [cocoaAction setTitle:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pMenu::constructor() {
+ @autoreleasepool {
+ cocoaAction = cocoaMenu = [[CocoaMenu alloc] initWith:menu];
+ setText(menu.state.text);
+ }
+}
+
+void pMenu::destructor() {
+ @autoreleasepool {
+ [[cocoaAction cocoaMenu] release];
+ [cocoaAction release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/action/menu.hpp b/higan/phoenix/cocoa/action/menu.hpp
new file mode 100644
index 00000000..51b3bf63
--- /dev/null
+++ b/higan/phoenix/cocoa/action/menu.hpp
@@ -0,0 +1,26 @@
+@interface CocoaMenu : NSMenuItem {
+@public
+ phoenix::Menu *menu;
+ NSMenu *cocoaMenu;
+}
+-(id) initWith :(phoenix::Menu&)menu;
+-(NSMenu*) cocoaMenu;
+@end
+
+namespace phoenix {
+
+struct pMenu : public pAction {
+ Menu &menu;
+ CocoaMenu *cocoaMenu;
+
+ void append(Action &action);
+ void remove(Action &action);
+ void setImage(const image &image);
+ void setText(const string &text);
+
+ pMenu(Menu &menu) : pAction(menu), menu(menu) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/action/radio-item.cpp b/higan/phoenix/cocoa/action/radio-item.cpp
new file mode 100644
index 00000000..2fa3f207
--- /dev/null
+++ b/higan/phoenix/cocoa/action/radio-item.cpp
@@ -0,0 +1,59 @@
+@implementation CocoaRadioItem : NSMenuItem
+
+-(id) initWith :(phoenix::RadioItem&)radioItemReference {
+ if(self = [super initWithTitle:@"" action:@selector(activate) keyEquivalent:@""]) {
+ radioItem = &radioItemReference;
+
+ [self setTarget:self];
+ [self setOnStateImage:[NSImage imageNamed:@"NSMenuRadio"]];
+ }
+ return self;
+}
+
+-(void) activate {
+ radioItem->setChecked();
+ if(radioItem->onActivate) radioItem->onActivate();
+}
+
+@end
+
+namespace phoenix {
+
+bool pRadioItem::checked() {
+ @autoreleasepool {
+ return [cocoaAction state] != NSOffState;
+ }
+}
+
+void pRadioItem::setChecked() {
+ @autoreleasepool {
+ for(auto &item : radioItem.state.group) {
+ auto state = (&item == &radioItem) ? NSOnState : NSOffState;
+ [item.p.cocoaAction setState:state];
+ }
+ }
+}
+
+void pRadioItem::setGroup(const set &group) {
+}
+
+void pRadioItem::setText(const string &text) {
+ @autoreleasepool {
+ [cocoaAction setTitle:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pRadioItem::constructor() {
+ @autoreleasepool {
+ cocoaAction = cocoaRadioItem = [[CocoaRadioItem alloc] initWith:radioItem];
+ setText(radioItem.state.text);
+ }
+}
+
+void pRadioItem::destructor() {
+ @autoreleasepool {
+ [cocoaAction release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/action/radio-item.hpp b/higan/phoenix/cocoa/action/radio-item.hpp
new file mode 100644
index 00000000..41d16669
--- /dev/null
+++ b/higan/phoenix/cocoa/action/radio-item.hpp
@@ -0,0 +1,25 @@
+@interface CocoaRadioItem : NSMenuItem {
+@public
+ phoenix::RadioItem *radioItem;
+}
+-(id) initWith :(phoenix::RadioItem&)radioItem;
+-(void) activate;
+@end
+
+namespace phoenix {
+
+struct pRadioItem : public pAction {
+ RadioItem &radioItem;
+ CocoaRadioItem *cocoaRadioItem;
+
+ bool checked();
+ void setChecked();
+ void setGroup(const set &group);
+ void setText(const string &text);
+
+ pRadioItem(RadioItem &radioItem) : pAction(radioItem), radioItem(radioItem) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/action/separator.cpp b/higan/phoenix/cocoa/action/separator.cpp
new file mode 100644
index 00000000..3b418389
--- /dev/null
+++ b/higan/phoenix/cocoa/action/separator.cpp
@@ -0,0 +1,26 @@
+@implementation CocoaSeparator : NSMenuItem
+
+-(id) initWith :(phoenix::Separator&)separatorReference {
+ if(self = [super separatorItem]) {
+ separator = &separatorReference;
+ }
+ return self;
+}
+
+@end
+
+namespace phoenix {
+
+void pSeparator::constructor() {
+ @autoreleasepool {
+ cocoaAction = cocoaSeparator = [[CocoaSeparator alloc] initWith:separator];
+ }
+}
+
+void pSeparator::destructor() {
+ @autoreleasepool {
+ [cocoaAction release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/action/separator.hpp b/higan/phoenix/cocoa/action/separator.hpp
new file mode 100644
index 00000000..e232b157
--- /dev/null
+++ b/higan/phoenix/cocoa/action/separator.hpp
@@ -0,0 +1,19 @@
+@interface CocoaSeparator : NSMenuItem {
+@public
+ phoenix::Separator *separator;
+}
+-(id) initWith :(phoenix::Separator&)separator;
+@end
+
+namespace phoenix {
+
+struct pSeparator : public pAction {
+ Separator &separator;
+ CocoaSeparator *cocoaSeparator;
+
+ pSeparator(Separator &separator) : pAction(separator), separator(separator) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/application.cpp b/higan/phoenix/cocoa/application.cpp
new file mode 100644
index 00000000..0c57c72b
--- /dev/null
+++ b/higan/phoenix/cocoa/application.cpp
@@ -0,0 +1,69 @@
+@implementation CocoaDelegate : NSObject
+
+-(NSApplicationTerminateReply) applicationShouldTerminate :(NSApplication*)sender {
+ using phoenix::Application;
+ if(Application::Cocoa::onQuit) Application::Cocoa::onQuit();
+ else Application::quit();
+ return NSTerminateCancel;
+}
+
+-(void) run :(NSTimer*)timer {
+ using phoenix::Application;
+ if(Application::main) Application::main();
+}
+
+@end
+
+CocoaDelegate *cocoaDelegate = nullptr;
+
+namespace phoenix {
+
+void pApplication::run() {
+ if(Application::main) {
+ NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.0 target:cocoaDelegate selector:@selector(run:) userInfo:nil repeats:YES];
+ }
+ @autoreleasepool {
+ [NSApp run];
+ }
+}
+
+bool pApplication::pendingEvents() {
+ bool result = false;
+ @autoreleasepool {
+ NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:NO];
+ if(event != nil) result = true;
+ }
+ return result;
+}
+
+void pApplication::processEvents() {
+ @autoreleasepool {
+ while(applicationState.quit == false) {
+ NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
+ if(event == nil) break;
+ [event retain];
+ [NSApp sendEvent:event];
+ [event release];
+ }
+ }
+}
+
+void pApplication::quit() {
+ @autoreleasepool {
+ [NSApp stop:nil];
+ NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0.0 windowNumber:0 context:nil subtype:0 data1:0 data2:0];
+ [NSApp postEvent:event atStart:true];
+ }
+}
+
+void pApplication::initialize() {
+ @autoreleasepool {
+ [NSApplication sharedApplication];
+ cocoaDelegate = [[CocoaDelegate alloc] init];
+ [NSApp setDelegate:cocoaDelegate];
+ //every window has the default application menu; call this so it is displayed at startup
+ [NSApp setMainMenu:[Window::none().p.cocoaWindow menu]];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/application.hpp b/higan/phoenix/cocoa/application.hpp
new file mode 100644
index 00000000..6f34d728
--- /dev/null
+++ b/higan/phoenix/cocoa/application.hpp
@@ -0,0 +1,18 @@
+@interface CocoaDelegate : NSObject {
+}
+-(NSApplicationTerminateReply) applicationShouldTerminate :(NSApplication*)sender;
+-(void) run :(NSTimer*)timer;
+@end
+
+namespace phoenix {
+
+struct pApplication {
+ static void run();
+ static bool pendingEvents();
+ static void processEvents();
+ static void quit();
+
+ static void initialize();
+};
+
+}
diff --git a/higan/phoenix/cocoa/desktop.cpp b/higan/phoenix/cocoa/desktop.cpp
new file mode 100644
index 00000000..f42cf43c
--- /dev/null
+++ b/higan/phoenix/cocoa/desktop.cpp
@@ -0,0 +1,18 @@
+namespace phoenix {
+
+Size pDesktop::size() {
+ @autoreleasepool {
+ NSRect primary = [[[NSScreen screens] objectAtIndex:0] frame];
+ return {primary.size.width, primary.size.height};
+ }
+}
+
+Geometry pDesktop::workspace() {
+ @autoreleasepool {
+ auto screen = Desktop::size();
+ NSRect area = [[[NSScreen screens] objectAtIndex:0] visibleFrame];
+ return {area.origin.x, screen.height - area.size.height - area.origin.y, area.size.width, area.size.height};
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/desktop.hpp b/higan/phoenix/cocoa/desktop.hpp
new file mode 100644
index 00000000..fed9ab6a
--- /dev/null
+++ b/higan/phoenix/cocoa/desktop.hpp
@@ -0,0 +1,8 @@
+namespace phoenix {
+
+struct pDesktop {
+ static Size size();
+ static Geometry workspace();
+};
+
+}
diff --git a/higan/phoenix/cocoa/dialog-window.cpp b/higan/phoenix/cocoa/dialog-window.cpp
new file mode 100644
index 00000000..e182a21a
--- /dev/null
+++ b/higan/phoenix/cocoa/dialog-window.cpp
@@ -0,0 +1,66 @@
+namespace phoenix {
+
+string pDialogWindow::fileOpen(Window &parent, const string &path, const lstring &filter) {
+ string result;
+
+ @autoreleasepool {
+ NSMutableArray *filters = [[NSMutableArray alloc] init];
+ for(auto &rule : filter) {
+ string pattern = rule.split<1>("(")(1).rtrim<1>(")");
+ if(!pattern.empty()) [filters addObject:[NSString stringWithUTF8String:pattern]];
+ }
+ NSOpenPanel *panel = [NSOpenPanel openPanel];
+ [panel setCanChooseDirectories:NO];
+ [panel setCanChooseFiles:YES];
+ [panel setAllowedFileTypes:filters];
+ if([panel runModalForDirectory:[NSString stringWithUTF8String:path] file:nil] == NSOKButton) {
+ NSArray *filenames = [panel filenames];
+ const char *filename = [[filenames objectAtIndex:0] UTF8String];
+ if(filename) result = filename;
+ }
+ [filters release];
+ }
+
+ return result;
+}
+
+string pDialogWindow::fileSave(Window &parent, const string &path, const lstring &filter) {
+ string result;
+
+ @autoreleasepool {
+ NSMutableArray *filters = [[NSMutableArray alloc] init];
+ for(auto &rule : filter) {
+ string pattern = rule.split<1>("(")(1).rtrim<1>(")");
+ if(!pattern.empty()) [filters addObjects:[NSString stringWithUTF8String:pattern]];
+ }
+ NSSavePanel *panel = [NSSavePanel savePanel];
+ [panel setAllowedFileTypes:filters];
+ if([panel runModalForDirectory:[NSString stringWithUTF8String:path] file:nil] == NSOKButton) {
+ NSArray *filenames = [panel filenames];
+ const char *filename = [[filenames objectAtIndex:0] UTF8String];
+ if(filename) result = filename;
+ }
+ [filters release];
+ }
+
+ return result;
+}
+
+string pDialogWindow::folderSelect(Window &parent, const string &path) {
+ string result;
+
+ @autoreleasepool {
+ NSOpenPanel *panel = [NSOpenPanel openPanel];
+ [panel setCanChooseDirectories:YES];
+ [panel setCanChooseFiles:NO];
+ if([panel runModalForDirectory:[NSString stringWithUTF8String:path] file:nil] == NSOKButton) {
+ NSArray *filenames = [panel filenames];
+ const char *filename = [[filenames objectAtIndex:0] UTF8String];
+ if(filename) result = filename;
+ }
+ }
+
+ return result;
+}
+
+}
diff --git a/higan/phoenix/cocoa/dialog-window.hpp b/higan/phoenix/cocoa/dialog-window.hpp
new file mode 100644
index 00000000..758ba2f5
--- /dev/null
+++ b/higan/phoenix/cocoa/dialog-window.hpp
@@ -0,0 +1,9 @@
+namespace phoenix {
+
+struct pDialogWindow {
+ static string fileOpen(Window &parent, const string &path, const lstring &filter);
+ static string fileSave(Window &parent, const string &path, const lstring &filter);
+ static string folderSelect(Window &parent, const string &path);
+};
+
+}
diff --git a/higan/phoenix/cocoa/font.cpp b/higan/phoenix/cocoa/font.cpp
new file mode 100644
index 00000000..7766a7a1
--- /dev/null
+++ b/higan/phoenix/cocoa/font.cpp
@@ -0,0 +1,59 @@
+namespace phoenix {
+
+string pFont::serif(unsigned size, string style) {
+ if(size == 0) size = 12;
+ if(style == "") style = "Normal";
+ return {"Georgia, ", size, ", ", style};
+}
+
+string pFont::sans(unsigned size, string style) {
+ if(size == 0) size = 12;
+ if(style == "") style = "Normal";
+ return {"Lucida Grande, ", size, ", ", style};
+}
+
+string pFont::monospace(unsigned size, string style) {
+ if(size == 0) size = 12;
+ if(style == "") style = "Normal";
+ return {"Menlo, ", size, ", ", style};
+}
+
+Size pFont::size(const string &font, const string &text) {
+ @autoreleasepool {
+ if(NSFont *nsFont = cocoaFont(font)) {
+ return size(nsFont, text);
+ }
+ }
+ return {0, 0};
+}
+
+NSFont* pFont::cocoaFont(const string &description) {
+ lstring part = description.split<2>(",");
+ for(auto &item : part) item.strip();
+
+ NSString *family = @"Lucida Grande";
+ NSFontTraitMask traits = 0;
+ CGFloat size = 12;
+
+ if(!part(0).empty()) family = [NSString stringWithUTF8String:part(0)];
+ if(!part(1).empty()) size = fp(part(1));
+ if(part(2).iposition("bold")) traits |= NSBoldFontMask;
+ if(part(2).iposition("italic")) traits |= NSItalicFontMask;
+ if(part(2).iposition("narrow")) traits |= NSNarrowFontMask;
+ if(part(2).iposition("expanded")) traits |= NSExpandedFontMask;
+ if(part(2).iposition("condensed")) traits |= NSCondensedFontMask;
+ if(part(2).iposition("smallcaps")) traits |= NSSmallCapsFontMask;
+
+ return [[NSFontManager sharedFontManager] fontWithFamily:family traits:traits weight:5 size:size];
+}
+
+Size pFont::size(NSFont *font, const string &text) {
+ @autoreleasepool {
+ NSString *cocoaText = [NSString stringWithUTF8String:text];
+ NSDictionary *fontAttributes = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
+ NSSize size = [cocoaText sizeWithAttributes:fontAttributes];
+ return {size.width, size.height};
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/font.hpp b/higan/phoenix/cocoa/font.hpp
new file mode 100644
index 00000000..50edac32
--- /dev/null
+++ b/higan/phoenix/cocoa/font.hpp
@@ -0,0 +1,13 @@
+namespace phoenix {
+
+struct pFont {
+ static string serif(unsigned size, string style);
+ static string sans(unsigned size, string style);
+ static string monospace(unsigned size, string style);
+ static Size size(const string &font, const string &text);
+
+ static NSFont* cocoaFont(const string &description);
+ static Size size(NSFont *font, const string &text);
+};
+
+}
diff --git a/higan/phoenix/cocoa/header.hpp b/higan/phoenix/cocoa/header.hpp
new file mode 100644
index 00000000..7e7f4999
--- /dev/null
+++ b/higan/phoenix/cocoa/header.hpp
@@ -0,0 +1,4 @@
+#define decimal CocoaDecimal
+#import
+#import
+#undef decimal
diff --git a/higan/phoenix/cocoa/keyboard.cpp b/higan/phoenix/cocoa/keyboard.cpp
new file mode 100644
index 00000000..adc28c87
--- /dev/null
+++ b/higan/phoenix/cocoa/keyboard.cpp
@@ -0,0 +1,14 @@
+namespace phoenix {
+
+bool pKeyboard::pressed(Keyboard::Scancode scancode) {
+ return false;
+}
+
+vector pKeyboard::state() {
+ vector output;
+ output.resize((unsigned)Keyboard::Scancode::Limit);
+ for(auto &n : output) n = false;
+ return output;
+}
+
+}
diff --git a/higan/phoenix/cocoa/keyboard.hpp b/higan/phoenix/cocoa/keyboard.hpp
new file mode 100644
index 00000000..2c0a42ae
--- /dev/null
+++ b/higan/phoenix/cocoa/keyboard.hpp
@@ -0,0 +1,8 @@
+namespace phoenix {
+
+struct pKeyboard {
+ static bool pressed(Keyboard::Scancode scancode);
+ static vector state();
+};
+
+}
diff --git a/higan/phoenix/cocoa/message-window.cpp b/higan/phoenix/cocoa/message-window.cpp
new file mode 100644
index 00000000..9337ea4e
--- /dev/null
+++ b/higan/phoenix/cocoa/message-window.cpp
@@ -0,0 +1,76 @@
+namespace phoenix {
+
+MessageWindow::Response pMessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) {
+ return message(parent, text, buttons, Type::Information);
+}
+
+MessageWindow::Response pMessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) {
+ return message(parent, text, buttons, Type::Question);
+}
+
+MessageWindow::Response pMessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) {
+ return message(parent, text, buttons, Type::Warning);
+}
+
+MessageWindow::Response pMessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) {
+ return message(parent, text, buttons, Type::Critical);
+}
+
+MessageWindow::Response pMessageWindow::message(Window &parent, const string &text, MessageWindow::Buttons buttons, Type type) {
+ @autoreleasepool {
+ NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+ [alert setMessageText:[NSString stringWithUTF8String:text]];
+
+ switch(buttons) {
+ case MessageWindow::Buttons::Ok:
+ [alert addButtonWithTitle:@"Ok"];
+ break;
+ case MessageWindow::Buttons::OkCancel:
+ [alert addButtonWithTitle:@"Ok"];
+ [alert addButtonWithTitle:@"Cancel"];
+ break;
+ case MessageWindow::Buttons::YesNo:
+ [alert addButtonWithTitle:@"Yes"];
+ [alert addButtonWithTitle:@"No"];
+ break;
+ case MessageWindow::Buttons::YesNoCancel:
+ [alert addButtonWithTitle:@"Yes"];
+ [alert addButtonWithTitle:@"No"];
+ [alert addButtonWithTitle:@"Cancel"];
+ break;
+ }
+
+ switch(type) {
+ case Type::Information: [alert setAlertStyle:NSInformationalAlertStyle]; break;
+ case Type::Question: [alert setAlertStyle:NSInformationalAlertStyle]; break;
+ case Type::Warning: [alert setAlertStyle:NSWarningAlertStyle]; break;
+ case Type::Critical: [alert setAlertStyle:NSCriticalAlertStyle]; break;
+ }
+
+ NSInteger response = [alert runModal];
+ //[alert beginSheetModalForWindow:parent.p.cocoaWindow modalDelegate:self didEndSelector:@selector(...) contextInfo:nil];
+
+ switch(buttons) {
+ case MessageWindow::Buttons::Ok:
+ if(response == NSAlertFirstButtonReturn) return MessageWindow::Response::Ok;
+ break;
+ case MessageWindow::Buttons::OkCancel:
+ if(response == NSAlertFirstButtonReturn) return MessageWindow::Response::Ok;
+ if(response == NSAlertSecondButtonReturn) return MessageWindow::Response::Cancel;
+ break;
+ case MessageWindow::Buttons::YesNo:
+ if(response == NSAlertFirstButtonReturn) return MessageWindow::Response::Yes;
+ if(response == NSAlertSecondButtonReturn) return MessageWindow::Response::No;
+ break;
+ case MessageWindow::Buttons::YesNoCancel:
+ if(response == NSAlertFirstButtonReturn) return MessageWindow::Response::Yes;
+ if(response == NSAlertSecondButtonReturn) return MessageWindow::Response::No;
+ if(response == NSAlertThirdButtonReturn) return MessageWindow::Response::Cancel;
+ break;
+ }
+ }
+
+ return MessageWindow::Response::Ok;
+}
+
+}
diff --git a/higan/phoenix/cocoa/message-window.hpp b/higan/phoenix/cocoa/message-window.hpp
new file mode 100644
index 00000000..ef470b1c
--- /dev/null
+++ b/higan/phoenix/cocoa/message-window.hpp
@@ -0,0 +1,13 @@
+namespace phoenix {
+
+struct pMessageWindow {
+ static MessageWindow::Response information(Window &parent, const string &text, MessageWindow::Buttons buttons);
+ static MessageWindow::Response question(Window &parent, const string &text, MessageWindow::Buttons buttons);
+ static MessageWindow::Response warning(Window &parent, const string &text, MessageWindow::Buttons buttons);
+ static MessageWindow::Response critical(Window &parent, const string &text, MessageWindow::Buttons buttons);
+
+ enum class Type : unsigned { Information, Question, Warning, Critical };
+ static MessageWindow::Response message(Window &parent, const string &text, MessageWindow::Buttons buttons, Type type);
+};
+
+}
diff --git a/higan/phoenix/cocoa/mouse.cpp b/higan/phoenix/cocoa/mouse.cpp
new file mode 100644
index 00000000..ca5e8556
--- /dev/null
+++ b/higan/phoenix/cocoa/mouse.cpp
@@ -0,0 +1,11 @@
+namespace phoenix {
+
+Position pMouse::position() {
+ return {0, 0};
+}
+
+bool pMouse::pressed(Mouse::Button button) {
+ return false;
+}
+
+}
diff --git a/higan/phoenix/cocoa/mouse.hpp b/higan/phoenix/cocoa/mouse.hpp
new file mode 100644
index 00000000..7e21f2ea
--- /dev/null
+++ b/higan/phoenix/cocoa/mouse.hpp
@@ -0,0 +1,8 @@
+namespace phoenix {
+
+struct pMouse {
+ static Position position();
+ static bool pressed(Mouse::Button button);
+};
+
+}
diff --git a/higan/phoenix/cocoa/object.hpp b/higan/phoenix/cocoa/object.hpp
new file mode 100644
index 00000000..f5102b5b
--- /dev/null
+++ b/higan/phoenix/cocoa/object.hpp
@@ -0,0 +1,14 @@
+namespace phoenix {
+
+struct pObject {
+ Object &object;
+ bool locked;
+
+ pObject(Object &object) : object(object), locked(false) {}
+ virtual ~pObject() {}
+
+ void constructor() {}
+ void destructor() {}
+};
+
+}
diff --git a/higan/phoenix/cocoa/platform.cpp b/higan/phoenix/cocoa/platform.cpp
new file mode 100644
index 00000000..fd7affbb
--- /dev/null
+++ b/higan/phoenix/cocoa/platform.cpp
@@ -0,0 +1,38 @@
+#include "platform.hpp"
+#include "utility.cpp"
+
+#include "desktop.cpp"
+#include "keyboard.cpp"
+#include "mouse.cpp"
+#include "dialog-window.cpp"
+#include "message-window.cpp"
+#include "font.cpp"
+#include "timer.cpp"
+#include "window.cpp"
+
+#include "action/action.cpp"
+#include "action/menu.cpp"
+#include "action/separator.cpp"
+#include "action/item.cpp"
+#include "action/check-item.cpp"
+#include "action/radio-item.cpp"
+
+#include "widget/widget.cpp"
+#include "widget/button.cpp"
+#include "widget/canvas.cpp"
+#include "widget/check-button.cpp"
+#include "widget/combo-button.cpp"
+#include "widget/hex-edit.cpp"
+#include "widget/horizontal-scroller.cpp"
+#include "widget/horizontal-slider.cpp"
+#include "widget/label.cpp"
+#include "widget/line-edit.cpp"
+#include "widget/list-view.cpp"
+#include "widget/progress-bar.cpp"
+#include "widget/radio-button.cpp"
+#include "widget/text-edit.cpp"
+#include "widget/vertical-scroller.cpp"
+#include "widget/vertical-slider.cpp"
+#include "widget/viewport.cpp"
+
+#include "application.cpp"
diff --git a/higan/phoenix/cocoa/platform.hpp b/higan/phoenix/cocoa/platform.hpp
new file mode 100644
index 00000000..fab4a5c6
--- /dev/null
+++ b/higan/phoenix/cocoa/platform.hpp
@@ -0,0 +1,46 @@
+namespace phoenix {
+ struct pFont;
+ struct pWindow;
+ struct pMenu;
+ struct pLayout;
+ struct pWidget;
+}
+
+#include "font.hpp"
+#include "desktop.hpp"
+#include "keyboard.hpp"
+#include "mouse.hpp"
+#include "dialog-window.hpp"
+#include "message-window.hpp"
+#include "object.hpp"
+#include "timer.hpp"
+#include "window.hpp"
+
+#include "action/action.hpp"
+#include "action/menu.hpp"
+#include "action/separator.hpp"
+#include "action/item.hpp"
+#include "action/check-item.hpp"
+#include "action/radio-item.hpp"
+
+#include "widget/sizable.hpp"
+#include "widget/layout.hpp"
+#include "widget/widget.hpp"
+#include "widget/button.hpp"
+#include "widget/canvas.hpp"
+#include "widget/check-button.hpp"
+#include "widget/combo-button.hpp"
+#include "widget/hex-edit.hpp"
+#include "widget/horizontal-scroller.hpp"
+#include "widget/horizontal-slider.hpp"
+#include "widget/label.hpp"
+#include "widget/line-edit.hpp"
+#include "widget/list-view.hpp"
+#include "widget/progress-bar.hpp"
+#include "widget/radio-button.hpp"
+#include "widget/text-edit.hpp"
+#include "widget/vertical-scroller.hpp"
+#include "widget/vertical-slider.hpp"
+#include "widget/viewport.hpp"
+
+#include "application.hpp"
diff --git a/higan/phoenix/cocoa/timer.cpp b/higan/phoenix/cocoa/timer.cpp
new file mode 100644
index 00000000..1b85bd65
--- /dev/null
+++ b/higan/phoenix/cocoa/timer.cpp
@@ -0,0 +1,60 @@
+@implementation CocoaTimer : NSObject
+
+-(id) initWith :(phoenix::Timer&)timerReference {
+ if(self = [super init]) {
+ timer = &timerReference;
+ instance = nil;
+ }
+ return self;
+}
+
+-(NSTimer*) instance {
+ return instance;
+}
+
+-(void) update {
+ if(instance) {
+ [instance invalidate];
+ instance = nil;
+ }
+ if(timer->state.enabled == false) return;
+ instance = [NSTimer
+ scheduledTimerWithTimeInterval:timer->state.milliseconds / 1000.0
+ target:self selector:@selector(run:) userInfo:nil repeats:YES
+ ];
+}
+
+-(void) run :(NSTimer*)instance {
+ if(timer->onActivate) timer->onActivate();
+}
+
+@end
+
+namespace phoenix {
+
+void pTimer::setEnabled(bool enabled) {
+ @autoreleasepool {
+ [cocoaTimer update];
+ }
+}
+
+void pTimer::setInterval(unsigned milliseconds) {
+ @autoreleasepool {
+ [cocoaTimer update];
+ }
+}
+
+void pTimer::constructor() {
+ @autoreleasepool {
+ cocoaTimer = [[CocoaTimer alloc] initWith:timer];
+ }
+}
+
+void pTimer::destructor() {
+ @autoreleasepool {
+ if([cocoaTimer instance]) [[cocoaTimer instance] invalidate];
+ [cocoaTimer release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/timer.hpp b/higan/phoenix/cocoa/timer.hpp
new file mode 100644
index 00000000..a4874bc0
--- /dev/null
+++ b/higan/phoenix/cocoa/timer.hpp
@@ -0,0 +1,26 @@
+@interface CocoaTimer : NSObject {
+@public
+ phoenix::Timer *timer;
+ NSTimer *instance;
+}
+-(id) initWith :(phoenix::Timer&)timer;
+-(NSTimer*) instance;
+-(void) update;
+-(void) run :(NSTimer*)instance;
+@end
+
+namespace phoenix {
+
+struct pTimer : public pObject {
+ Timer &timer;
+ CocoaTimer *cocoaTimer;
+
+ void setEnabled(bool enabled);
+ void setInterval(unsigned milliseconds);
+
+ pTimer(Timer &timer) : pObject(timer), timer(timer) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/utility.cpp b/higan/phoenix/cocoa/utility.cpp
new file mode 100644
index 00000000..ad5a6cb0
--- /dev/null
+++ b/higan/phoenix/cocoa/utility.cpp
@@ -0,0 +1,17 @@
+NSImage* NSMakeImage(nall::image image, unsigned width = 0, unsigned height = 0) {
+ if(image.empty()) return nil;
+ if(width && height) image.scale(width, height, Interpolation::Linear);
+ image.transform(0, 32, 255u << 24, 255u << 0, 255u << 8, 255u << 16);
+ NSImage *cocoaImage = [[[NSImage alloc] initWithSize:NSMakeSize(image.width, image.height)] autorelease];
+ NSBitmapImageRep *bitmap = [[[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nil
+ pixelsWide:image.width pixelsHigh:image.height
+ bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES
+ isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace
+ bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
+ bytesPerRow:image.pitch bitsPerPixel:32
+ ] autorelease];
+ memcpy([bitmap bitmapData], image.data, image.height * image.pitch);
+ [cocoaImage addRepresentation:bitmap];
+ return cocoaImage;
+}
diff --git a/higan/phoenix/cocoa/widget/button.cpp b/higan/phoenix/cocoa/widget/button.cpp
new file mode 100644
index 00000000..24797731
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/button.cpp
@@ -0,0 +1,70 @@
+@implementation CocoaButton : NSButton
+
+-(id) initWith :(phoenix::Button&)buttonReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0)]) {
+ button = &buttonReference;
+ [self setTarget:self];
+ [self setAction:@selector(activate:)];
+ //NSRoundedBezelStyle has a fixed height; which breaks both icons and larger/smaller text
+ [self setBezelStyle:NSRegularSquareBezelStyle];
+ }
+ return self;
+}
+
+-(IBAction) activate :(id)sender {
+ if(button->onActivate) button->onActivate();
+}
+
+@end
+
+namespace phoenix {
+
+Size pButton::minimumSize() {
+ Size size = Font::size(button.font(), button.state.text);
+
+ if(button.state.orientation == Orientation::Horizontal) {
+ size.width += button.state.image.width;
+ size.height = max(button.state.image.height, size.height);
+ }
+
+ if(button.state.orientation == Orientation::Vertical) {
+ size.width = max(button.state.image.width, size.width);
+ size.height += button.state.image.height;
+ }
+
+ return {size.width + 24, size.height + 8};
+}
+
+void pButton::setImage(const image &image, Orientation orientation) {
+ @autoreleasepool {
+ if(image.empty()) {
+ [cocoaView setImage:nil];
+ return;
+ }
+
+ [cocoaView setImage:NSMakeImage(image)];
+
+ if(orientation == Orientation::Horizontal) [cocoaView setImagePosition:NSImageLeft];
+ if(orientation == Orientation::Vertical ) [cocoaView setImagePosition:NSImageAbove];
+ }
+}
+
+void pButton::setText(const string &text) {
+ @autoreleasepool {
+ [cocoaView setTitle:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pButton::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaButton = [[CocoaButton alloc] initWith:button];
+ }
+}
+
+void pButton::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/button.hpp b/higan/phoenix/cocoa/widget/button.hpp
new file mode 100644
index 00000000..367ca19a
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/button.hpp
@@ -0,0 +1,24 @@
+@interface CocoaButton : NSButton {
+@public
+ phoenix::Button *button;
+}
+-(id) initWith :(phoenix::Button&)button;
+-(IBAction) activate :(id)sender;
+@end
+
+namespace phoenix {
+
+struct pButton : public pWidget {
+ Button &button;
+ CocoaButton *cocoaButton;
+
+ Size minimumSize();
+ void setImage(const image &image, Orientation orientation);
+ void setText(const string &text);
+
+ pButton(Button &button) : pWidget(button), button(button) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/canvas.cpp b/higan/phoenix/cocoa/widget/canvas.cpp
new file mode 100644
index 00000000..d7d654c5
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/canvas.cpp
@@ -0,0 +1,125 @@
+@implementation CocoaCanvas : NSImageView
+
+-(id) initWith :(phoenix::Canvas&)canvasReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0)]) {
+ canvas = &canvasReference;
+ [self setEditable:NO]; //disable image drag-and-drop functionality
+ NSTrackingArea *area = [[[NSTrackingArea alloc] initWithRect:[self frame]
+ options:NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect
+ owner:self userInfo:nil
+ ] autorelease];
+ [self addTrackingArea:area];
+ }
+ return self;
+}
+
+-(void) mouseButton :(NSEvent*)event down:(BOOL)isDown {
+ if(auto &callback = isDown ? canvas->onMousePress : canvas->onMouseRelease) {
+ switch([event buttonNumber]) {
+ case 0: return callback(phoenix::Mouse::Button::Left);
+ case 1: return callback(phoenix::Mouse::Button::Right);
+ case 2: return callback(phoenix::Mouse::Button::Middle);
+ }
+ }
+}
+
+-(void) mouseExited :(NSEvent*)event {
+ if(canvas->onMouseLeave) canvas->onMouseLeave();
+}
+
+-(void) mouseMove :(NSEvent*)event {
+ if([event window] == nil) return;
+ NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil];
+ if(canvas->onMouseMove) canvas->onMouseMove({location.x, [self frame].size.height - 1 - location.y});
+}
+
+-(void) mouseDown :(NSEvent*)event {
+ [self mouseButton:event down:YES];
+}
+
+-(void) mouseUp :(NSEvent*)event {
+ [self mouseButton:event down:NO];
+}
+
+-(void) mouseDragged :(NSEvent*)event {
+ [self mouseMove:event];
+}
+
+-(void) rightMouseDown :(NSEvent*)event {
+ [self mouseButton:event down:YES];
+}
+
+-(void) rightMouseUp :(NSEvent*)event {
+ [self mouseButton:event down:NO];
+}
+
+-(void) rightMouseDragged :(NSEvent*)event {
+ [self mouseMove:event];
+}
+
+-(void) otherMouseDown :(NSEvent*)event {
+ [self mouseButton:event down:YES];
+}
+
+-(void) otherMouseUp :(NSEvent*)event {
+ [self mouseButton:event down:NO];
+}
+
+-(void) otherMouseDragged :(NSEvent*)event {
+ [self mouseMove:event];
+}
+
+@end
+
+namespace phoenix {
+
+void pCanvas::setSize(const Size &size) {
+ @autoreleasepool {
+ NSImage *image = [[[NSImage alloc] initWithSize:NSMakeSize(size.width, size.height)] autorelease];
+ NSBitmapImageRep *bitmap = [[[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:nil
+ pixelsWide:size.width pixelsHigh:size.height
+ bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES
+ isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace
+ bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
+ bytesPerRow:size.width * 4 bitsPerPixel:32
+ ] autorelease];
+
+ [image addRepresentation:bitmap];
+ [cocoaView setImage:image];
+ }
+}
+
+void pCanvas::update() {
+ @autoreleasepool {
+ if(NSBitmapImageRep *bitmap = [[[cocoaView image] representations] objectAtIndex:0]) {
+ uint8_t *target = [bitmap bitmapData];
+ uint32_t *source = canvas.state.data;
+
+ for(unsigned n = 0; n < canvas.state.width * canvas.state.height; n++) {
+ *target++ = *source >> 16;
+ *target++ = *source >> 8;
+ *target++ = *source >> 0;
+ *target++ = *source >> 24;
+ source++;
+ }
+
+ [cocoaView setNeedsDisplay:YES];
+ }
+ }
+}
+
+void pCanvas::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaCanvas = [[CocoaCanvas alloc] initWith:canvas];
+ setSize(canvas.size());
+ }
+}
+
+void pCanvas::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/canvas.hpp b/higan/phoenix/cocoa/widget/canvas.hpp
new file mode 100644
index 00000000..d3fb3936
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/canvas.hpp
@@ -0,0 +1,34 @@
+@interface CocoaCanvas : NSImageView {
+@public
+ phoenix::Canvas *canvas;
+}
+-(id) initWith:(phoenix::Canvas&)canvas;
+-(void) mouseButton :(NSEvent*)event down:(BOOL)isDown;
+-(void) mouseExited :(NSEvent*)event;
+-(void) mouseMove :(NSEvent*)event;
+-(void) mouseDown :(NSEvent*)event;
+-(void) mouseUp :(NSEvent*)event;
+-(void) mouseDragged :(NSEvent*)event;
+-(void) rightMouseDown :(NSEvent*)event;
+-(void) rightMouseUp :(NSEvent*)event;
+-(void) rightMouseDragged :(NSEvent*)event;
+-(void) otherMouseDown :(NSEvent*)event;
+-(void) otherMouseUp :(NSEvent*)event;
+-(void) otherMouseDragged :(NSEvent*)event;
+@end
+
+namespace phoenix {
+
+struct pCanvas : public pWidget {
+ Canvas &canvas;
+ CocoaCanvas *cocoaCanvas;
+
+ void setSize(const Size &size);
+ void update();
+
+ pCanvas(Canvas &canvas) : pWidget(canvas), canvas(canvas) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/check-button.cpp b/higan/phoenix/cocoa/widget/check-button.cpp
new file mode 100644
index 00000000..68afe838
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/check-button.cpp
@@ -0,0 +1,60 @@
+@implementation CocoaCheckButton : NSButton
+
+-(id) initWith :(phoenix::CheckButton&)checkButtonReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0)]) {
+ checkButton = &checkButtonReference;
+
+ [self setTarget:self];
+ [self setAction:@selector(activate:)];
+ [self setButtonType:NSSwitchButton];
+ }
+ return self;
+}
+
+-(IBAction) activate :(id)sender {
+ checkButton->state.checked = [self state] != NSOffState;
+ if(checkButton->onToggle) checkButton->onToggle();
+}
+
+@end
+
+namespace phoenix {
+
+bool pCheckButton::checked() {
+ @autoreleasepool {
+ return [cocoaView state] != NSOffState;
+ }
+}
+
+Size pCheckButton::minimumSize() {
+ Size size = Font::size(checkButton.font(), checkButton.state.text);
+ return {size.width + 24, size.height + 8};
+}
+
+void pCheckButton::setChecked(bool checked) {
+ @autoreleasepool {
+ [cocoaView setState:checked ? NSOnState : NSOffState];
+ }
+}
+
+void pCheckButton::setText(const string &text) {
+ @autoreleasepool {
+ [cocoaView setTitle:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pCheckButton::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaCheckButton = [[CocoaCheckButton alloc] initWith:checkButton];
+ setChecked(checkButton.state.checked);
+ setText(checkButton.state.text);
+ }
+}
+
+void pCheckButton::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/check-button.hpp b/higan/phoenix/cocoa/widget/check-button.hpp
new file mode 100644
index 00000000..52d240e5
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/check-button.hpp
@@ -0,0 +1,25 @@
+@interface CocoaCheckButton : NSButton {
+@public
+ phoenix::CheckButton *checkButton;
+}
+-(id) initWith :(phoenix::CheckButton&)checkButton;
+-(IBAction) activate :(id)sender;
+@end
+
+namespace phoenix {
+
+struct pCheckButton : public pWidget {
+ CheckButton &checkButton;
+ CocoaCheckButton *cocoaCheckButton;
+
+ bool checked();
+ Size minimumSize();
+ void setChecked(bool checked);
+ void setText(const string &text);
+
+ pCheckButton(CheckButton &checkButton) : pWidget(checkButton), checkButton(checkButton) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/combo-button.cpp b/higan/phoenix/cocoa/widget/combo-button.cpp
new file mode 100644
index 00000000..72226cdb
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/combo-button.cpp
@@ -0,0 +1,76 @@
+@implementation CocoaComboButton : NSPopUpButton
+
+-(id) initWith :(phoenix::ComboButton&)comboButtonReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0) pullsDown:NO]) {
+ comboButton = &comboButtonReference;
+
+ [self setTarget:self];
+ [self setAction:@selector(activate:)];
+ }
+ return self;
+}
+
+-(IBAction) activate :(id)sender {
+ if(comboButton->onChange) comboButton->onChange();
+}
+
+@end
+
+namespace phoenix {
+
+void pComboButton::append(const string &text) {
+ @autoreleasepool {
+ [cocoaView addItemWithTitle:[NSString stringWithUTF8String:text]];
+ }
+}
+
+Size pComboButton::minimumSize() {
+ unsigned maximumWidth = 0;
+ for(auto &text : comboButton.state.text) maximumWidth = max(maximumWidth, Font::size(comboButton.font(), text).width);
+ Size size = Font::size(comboButton.font(), " ");
+ return {maximumWidth + 40, size.height + 8};
+}
+
+void pComboButton::modify(unsigned row, const string &text) {
+ @autoreleasepool {
+ [[cocoaView itemAtIndex:row] setTitle:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pComboButton::remove(unsigned row) {
+ @autoreleasepool {
+ [cocoaView removeItemAtIndex:row];
+ }
+}
+
+void pComboButton::reset() {
+ @autoreleasepool {
+ [cocoaView removeAllItems];
+ }
+}
+
+unsigned pComboButton::selection() {
+ @autoreleasepool {
+ return [cocoaView indexOfSelectedItem];
+ }
+}
+
+void pComboButton::setSelection(unsigned row) {
+ @autoreleasepool {
+ [cocoaView selectItemAtIndex:row];
+ }
+}
+
+void pComboButton::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaComboButton = [[CocoaComboButton alloc] initWith:comboButton];
+ }
+}
+
+void pComboButton::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/combo-button.hpp b/higan/phoenix/cocoa/widget/combo-button.hpp
new file mode 100644
index 00000000..7701155c
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/combo-button.hpp
@@ -0,0 +1,28 @@
+@interface CocoaComboButton : NSPopUpButton {
+@public
+ phoenix::ComboButton *comboButton;
+}
+-(id) initWith :(phoenix::ComboButton&)comboButton;
+-(IBAction) activate :(id)sender;
+@end
+
+namespace phoenix {
+
+struct pComboButton : public pWidget {
+ ComboButton &comboButton;
+ CocoaComboButton *cocoaComboButton;
+
+ void append(const string &text);
+ Size minimumSize();
+ void modify(unsigned row, const string &text);
+ void remove(unsigned row);
+ void reset();
+ unsigned selection();
+ void setSelection(unsigned row);
+
+ pComboButton(ComboButton &comboButton) : pWidget(comboButton), comboButton(comboButton) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/hex-edit.cpp b/higan/phoenix/cocoa/widget/hex-edit.cpp
new file mode 100644
index 00000000..95d8b72a
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/hex-edit.cpp
@@ -0,0 +1,21 @@
+namespace phoenix {
+
+void pHexEdit::setColumns(unsigned columns) {
+}
+
+void pHexEdit::setLength(unsigned length) {
+}
+
+void pHexEdit::setOffset(unsigned offset) {
+}
+
+void pHexEdit::setRows(unsigned rows) {
+}
+
+void pHexEdit::update() {
+}
+
+void pHexEdit::constructor() {
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/hex-edit.hpp b/higan/phoenix/cocoa/widget/hex-edit.hpp
new file mode 100644
index 00000000..5819ee4d
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/hex-edit.hpp
@@ -0,0 +1,16 @@
+namespace phoenix {
+
+struct pHexEdit : public pWidget {
+ HexEdit &hexEdit;
+
+ void setColumns(unsigned columns);
+ void setLength(unsigned length);
+ void setOffset(unsigned offset);
+ void setRows(unsigned rows);
+ void update();
+
+ pHexEdit(HexEdit &hexEdit) : pWidget(hexEdit), hexEdit(hexEdit) {}
+ void constructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/horizontal-scroller.cpp b/higan/phoenix/cocoa/widget/horizontal-scroller.cpp
new file mode 100644
index 00000000..67b59ffe
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/horizontal-scroller.cpp
@@ -0,0 +1,91 @@
+@implementation CocoaHorizontalScroller : NSScroller
+
+-(id) initWith :(phoenix::HorizontalScroller&)horizontalScrollerReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 1, 0)]) {
+ horizontalScroller = &horizontalScrollerReference;
+
+ [self setTarget:self];
+ [self setAction:@selector(scroll:)];
+
+ [self setControlSize:NSRegularControlSize];
+ [self setScrollerStyle:NSScrollerStyleLegacy];
+ [self setEnabled:YES];
+
+ [self update];
+ }
+ return self;
+}
+
+-(void) update {
+ double d = 1.0 / horizontalScroller->state.length;
+ double f = d * horizontalScroller->state.position;
+
+ [self setDoubleValue:f];
+ [self setKnobProportion:d];
+}
+
+-(IBAction) scroll :(id)sender {
+ auto &state = horizontalScroller->state;
+
+ switch([self hitPart]) {
+ case NSScrollerIncrementLine:
+ case NSScrollerIncrementPage:
+ if(state.position < state.length - 1) state.position++;
+ [self update];
+ break;
+
+ case NSScrollerDecrementLine:
+ case NSScrollerDecrementPage:
+ if(state.position) state.position--;
+ [self update];
+ break;
+
+ case NSScrollerKnob:
+ state.position = [self doubleValue] * state.length;
+ break;
+ }
+
+ if(horizontalScroller->onChange) horizontalScroller->onChange();
+}
+
+@end
+
+namespace phoenix {
+
+Size pHorizontalScroller::minimumSize() {
+ @autoreleasepool {
+ return {32, [NSScroller scrollerWidthForControlSize:NSRegularControlSize scrollerStyle:NSScrollerStyleLegacy]};
+ }
+}
+
+unsigned pHorizontalScroller::position() {
+ @autoreleasepool {
+ return [cocoaView doubleValue] * horizontalScroller.state.length;
+ }
+}
+
+void pHorizontalScroller::setLength(unsigned length) {
+ @autoreleasepool {
+ [cocoaView update];
+ }
+}
+
+void pHorizontalScroller::setPosition(unsigned position) {
+ @autoreleasepool {
+ [cocoaView update];
+ }
+}
+
+void pHorizontalScroller::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaHorizontalScroller = [[CocoaHorizontalScroller alloc] initWith:horizontalScroller];
+ }
+}
+
+void pHorizontalScroller::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/horizontal-scroller.hpp b/higan/phoenix/cocoa/widget/horizontal-scroller.hpp
new file mode 100644
index 00000000..1756705b
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/horizontal-scroller.hpp
@@ -0,0 +1,26 @@
+@interface CocoaHorizontalScroller : NSScroller {
+@public
+ phoenix::HorizontalScroller *horizontalScroller;
+}
+-(id) initWith :(phoenix::HorizontalScroller&)horizontalScroller;
+-(void) update;
+-(IBAction) scroll :(id)sender;
+@end
+
+namespace phoenix {
+
+struct pHorizontalScroller : public pWidget {
+ HorizontalScroller &horizontalScroller;
+ CocoaHorizontalScroller *cocoaHorizontalScroller;
+
+ Size minimumSize();
+ unsigned position();
+ void setLength(unsigned length);
+ void setPosition(unsigned position);
+
+ pHorizontalScroller(HorizontalScroller &horizontalScroller) : pWidget(horizontalScroller), horizontalScroller(horizontalScroller) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/horizontal-slider.cpp b/higan/phoenix/cocoa/widget/horizontal-slider.cpp
new file mode 100644
index 00000000..aa0976ed
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/horizontal-slider.cpp
@@ -0,0 +1,60 @@
+@implementation CocoaHorizontalSlider : NSSlider
+
+-(id) initWith :(phoenix::HorizontalSlider&)horizontalSliderReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 1, 0)]) {
+ horizontalSlider = &horizontalSliderReference;
+
+ [self setTarget:self];
+ [self setAction:@selector(activate:)];
+ [self setMinValue:0];
+ }
+ return self;
+}
+
+-(IBAction) activate :(id)sender {
+ horizontalSlider->state.position = [self doubleValue];
+ if(horizontalSlider->onChange) horizontalSlider->onChange();
+}
+
+@end
+
+namespace phoenix {
+
+Size pHorizontalSlider::minimumSize() {
+ return {64, 24};
+}
+
+unsigned pHorizontalSlider::position() {
+ @autoreleasepool {
+ return [cocoaView doubleValue];
+ }
+}
+
+void pHorizontalSlider::setLength(unsigned length) {
+ @autoreleasepool {
+ [cocoaView setMaxValue:length];
+ }
+}
+
+void pHorizontalSlider::setPosition(unsigned position) {
+ @autoreleasepool {
+ [cocoaView setDoubleValue:position];
+ }
+}
+
+void pHorizontalSlider::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaHorizontalSlider = [[CocoaHorizontalSlider alloc] initWith:horizontalSlider];
+
+ setLength(horizontalSlider.state.length);
+ setPosition(horizontalSlider.state.position);
+ }
+}
+
+void pHorizontalSlider::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/horizontal-slider.hpp b/higan/phoenix/cocoa/widget/horizontal-slider.hpp
new file mode 100644
index 00000000..d60b6a0c
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/horizontal-slider.hpp
@@ -0,0 +1,25 @@
+@interface CocoaHorizontalSlider : NSSlider {
+@public
+ phoenix::HorizontalSlider *horizontalSlider;
+}
+-(id) initWith :(phoenix::HorizontalSlider&)horizontalSlider;
+-(IBAction) activate :(id)sender;
+@end
+
+namespace phoenix {
+
+struct pHorizontalSlider : public pWidget {
+ HorizontalSlider &horizontalSlider;
+ CocoaHorizontalSlider *cocoaHorizontalSlider;
+
+ Size minimumSize();
+ unsigned position();
+ void setLength(unsigned length);
+ void setPosition(unsigned position);
+
+ pHorizontalSlider(HorizontalSlider &horizontalSlider) : pWidget(horizontalSlider), horizontalSlider(horizontalSlider) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/label.cpp b/higan/phoenix/cocoa/widget/label.cpp
new file mode 100644
index 00000000..24eb5e0e
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/label.cpp
@@ -0,0 +1,43 @@
+@implementation CocoaLabel : NSTextField
+
+-(id) initWith :(phoenix::Label&)labelReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0)]) {
+ label = &labelReference;
+
+ [self setAlignment:NSLeftTextAlignment];
+ [self setBordered:NO];
+ [self setDrawsBackground:NO];
+ [self setEditable:NO];
+ }
+ return self;
+}
+
+@end
+
+namespace phoenix {
+
+Size pLabel::minimumSize() {
+ Size size = Font::size(label.font(), label.state.text);
+ return {size.width, size.height};
+}
+
+void pLabel::setText(const string &text) {
+ @autoreleasepool {
+ [cocoaView setStringValue:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pLabel::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaLabel = [[CocoaLabel alloc] initWith:label];
+ setText(label.state.text);
+ }
+}
+
+void pLabel::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/label.hpp b/higan/phoenix/cocoa/widget/label.hpp
new file mode 100644
index 00000000..69466ebf
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/label.hpp
@@ -0,0 +1,22 @@
+@interface CocoaLabel : NSTextField {
+@public
+ phoenix::Label *label;
+}
+-(id) initWith :(phoenix::Label&)label;
+@end
+
+namespace phoenix {
+
+struct pLabel : public pWidget {
+ Label &label;
+ CocoaLabel *cocoaLabel;
+
+ Size minimumSize();
+ void setText(const string &text);
+
+ pLabel(Label &label) : pWidget(label), label(label) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/layout.hpp b/higan/phoenix/cocoa/widget/layout.hpp
new file mode 100644
index 00000000..100b7463
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/layout.hpp
@@ -0,0 +1,9 @@
+namespace phoenix {
+
+struct pLayout : public pSizable {
+ Layout &layout;
+
+ pLayout(Layout &layout) : pSizable(layout), layout(layout) {}
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/line-edit.cpp b/higan/phoenix/cocoa/widget/line-edit.cpp
new file mode 100644
index 00000000..9e68cfe7
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/line-edit.cpp
@@ -0,0 +1,65 @@
+@implementation CocoaLineEdit : NSTextField
+
+-(id) initWith :(phoenix::LineEdit&)lineEditReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0)]) {
+ lineEdit = &lineEditReference;
+
+ [self setDelegate:self];
+ [self setTarget:self];
+ [self setAction:@selector(activate:)];
+
+ //prevent focus changes from generating activate event
+ [[self cell] setSendsActionOnEndEditing:NO];
+ }
+ return self;
+}
+
+-(void) textDidChange :(NSNotification*)n {
+ if(lineEdit->onChange) lineEdit->onChange();
+}
+
+-(IBAction) activate :(id)sender {
+ if(lineEdit->onActivate) lineEdit->onActivate();
+}
+
+@end
+
+namespace phoenix {
+
+Size pLineEdit::minimumSize() {
+ Size size = Font::size(lineEdit.font(), lineEdit.state.text);
+ return {size.width + 10, size.height + 8};
+}
+
+void pLineEdit::setEditable(bool editable) {
+ @autoreleasepool {
+ [cocoaView setEditable:editable];
+ }
+}
+
+void pLineEdit::setText(const string &text) {
+ @autoreleasepool {
+ [cocoaView setStringValue:[NSString stringWithUTF8String:text]];
+ }
+}
+
+string pLineEdit::text() {
+ @autoreleasepool {
+ return [[cocoaView stringValue] UTF8String];
+ }
+}
+
+void pLineEdit::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaLineEdit = [[CocoaLineEdit alloc] initWith:lineEdit];
+ setEditable(lineEdit.state.editable);
+ }
+}
+
+void pLineEdit::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/line-edit.hpp b/higan/phoenix/cocoa/widget/line-edit.hpp
new file mode 100644
index 00000000..97cfe7c6
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/line-edit.hpp
@@ -0,0 +1,26 @@
+@interface CocoaLineEdit : NSTextField {
+@public
+ phoenix::LineEdit *lineEdit;
+}
+-(id) initWith :(phoenix::LineEdit&)lineEdit;
+-(void) textDidChange :(NSNotification*)n;
+-(IBAction) activate :(id)sender;
+@end
+
+namespace phoenix {
+
+struct pLineEdit : public pWidget {
+ LineEdit &lineEdit;
+ CocoaLineEdit *cocoaLineEdit;
+
+ Size minimumSize();
+ void setEditable(bool editable);
+ void setText(const string &text);
+ string text();
+
+ pLineEdit(LineEdit &lineEdit) : pWidget(lineEdit), lineEdit(lineEdit) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/list-view.cpp b/higan/phoenix/cocoa/widget/list-view.cpp
new file mode 100644
index 00000000..f8bf1042
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/list-view.cpp
@@ -0,0 +1,326 @@
+@implementation CocoaListView : NSScrollView
+
+-(id) initWith :(phoenix::ListView&)listViewReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0)]) {
+ listView = &listViewReference;
+ content = [[CocoaListViewContent alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)];
+
+ [self setDocumentView:content];
+ [self setBorderType:NSBezelBorder];
+ [self setHasVerticalScroller:YES];
+
+ [content setDataSource:self];
+ [content setDelegate:self];
+ [content setTarget:self];
+ [content setDoubleAction:@selector(activate:)];
+
+ [content setAllowsColumnReordering:NO];
+ [content setAllowsColumnResizing:YES];
+ [content setAllowsColumnSelection:NO];
+ [content setAllowsEmptySelection:YES];
+ [content setAllowsMultipleSelection:NO];
+ [content setColumnAutoresizingStyle:NSTableViewNoColumnAutoresizing];
+
+ font = nil;
+ [self setFont:nil];
+ }
+ return self;
+}
+
+-(void) dealloc {
+ [content release];
+ [font release];
+ [super dealloc];
+}
+
+-(CocoaListViewContent*) content {
+ return content;
+}
+
+-(NSFont*) font {
+ return font;
+}
+
+-(void) setFont :(NSFont*)fontPointer {
+ if(!fontPointer) fontPointer = [NSFont systemFontOfSize:12];
+ [fontPointer retain];
+ if(font) [font release];
+ font = fontPointer;
+
+ unsigned fontHeight = phoenix::pFont::size(font, " ").height;
+ [content setFont:font];
+ [content setRowHeight:fontHeight];
+ [self reloadColumns];
+}
+
+-(void) reloadColumns {
+ while([[content tableColumns] count]) {
+ [content removeTableColumn:[[content tableColumns] lastObject]];
+ }
+
+ if(listView->state.checkable) {
+ NSTableColumn *tableColumn = [[NSTableColumn alloc] initWithIdentifier:@"check"];
+ NSTableHeaderCell *headerCell = [[NSTableHeaderCell alloc] initTextCell:@""];
+ NSButtonCell *dataCell = [[NSButtonCell alloc] initTextCell:@""];
+
+ [dataCell setButtonType:NSSwitchButton];
+ [dataCell setControlSize:NSSmallControlSize];
+ [dataCell setRefusesFirstResponder:YES];
+
+ [tableColumn setResizingMask:NSTableColumnNoResizing];
+ [tableColumn setHeaderCell:headerCell];
+ [tableColumn setDataCell:dataCell];
+ [tableColumn setWidth:20.0];
+
+ [content addTableColumn:tableColumn];
+ }
+
+ lstring headers = listView->state.headerText;
+ if(headers.size() == 0) headers.append("");
+ [content setUsesAlternatingRowBackgroundColors:headers.size() >= 2];
+
+ for(unsigned column = 0; column < headers.size(); column++) {
+ NSTableColumn *tableColumn = [[NSTableColumn alloc] initWithIdentifier:[[NSNumber numberWithInteger:column] stringValue]];
+ NSTableHeaderCell *headerCell = [[NSTableHeaderCell alloc] initTextCell:[NSString stringWithUTF8String:headers(column)]];
+ CocoaListViewCell *dataCell = [[CocoaListViewCell alloc] initTextCell:@""];
+
+ [dataCell setEditable:NO];
+
+ [tableColumn setResizingMask:NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask];
+ [tableColumn setHeaderCell:headerCell];
+ [tableColumn setDataCell:dataCell];
+
+ [content addTableColumn:tableColumn];
+ }
+}
+
+-(NSInteger) numberOfRowsInTableView :(NSTableView*)table {
+ return listView->state.text.size();
+}
+
+-(id) tableView :(NSTableView*)table objectValueForTableColumn :(NSTableColumn*)tableColumn row:(NSInteger)row {
+ if([[tableColumn identifier] isEqualToString:@"check"]) {
+ auto checked = listView->state.checked(row) ? NSOnState : NSOffState;
+ return [NSNumber numberWithInteger:checked];
+ }
+
+ NSInteger column = [[tableColumn identifier] integerValue];
+ unsigned height = [table rowHeight];
+
+ NSString *text = [NSString stringWithUTF8String:listView->state.text(row)(column)];
+ NSImage *image = NSMakeImage(listView->state.image(row)(column), height, height);
+
+ if(image) return @{ @"text":text, @"image":image };
+ return @{ @"text":text };
+}
+
+-(void) tableView :(NSTableView*)table setObjectValue:(id)object forTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row {
+ if([[tableColumn identifier] isEqualToString:@"check"]) {
+ listView->state.checked(row) = [object integerValue] != NSOffState;
+ if(listView->onToggle) listView->onToggle(row);
+ }
+}
+
+-(void) tableView :(NSTableView*)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row {
+ [cell setFont:[self font]];
+}
+
+-(void) tableViewSelectionDidChange :(NSNotification*)notification {
+ if(listView->onChange) listView->onChange();
+}
+
+-(IBAction) activate :(id)sender {
+ if([content clickedRow] < 0) return;
+ if(listView->onActivate) listView->onActivate();
+}
+
+@end
+
+@implementation CocoaListViewContent : NSTableView
+
+-(void) keyDown :(NSEvent*)event {
+ [super keyDown:event];
+
+ auto character = [[event characters] characterAtIndex:0];
+ if(character == NSEnterCharacter || character == NSCarriageReturnCharacter) {
+ [[self delegate] activate:self];
+ }
+}
+
+@end
+
+@implementation CocoaListViewCell : NSTextFieldCell
+
+//used by type-ahead
+-(NSString*) stringValue {
+ return [[self objectValue] objectForKey:@"text"];
+}
+
+-(void) drawWithFrame :(NSRect)frame inView:(NSView*)view {
+ NSString *text = [[self objectValue] objectForKey:@"text"];
+ NSImage *image = [[self objectValue] objectForKey:@"image"];
+ unsigned textDisplacement = 0;
+
+ if(image) {
+ NSGraphicsContext *context = [NSGraphicsContext currentContext];
+ [context saveGraphicsState];
+
+ NSRect targetRect = NSMakeRect(frame.origin.x, frame.origin.y, frame.size.height, frame.size.height);
+ NSRect sourceRect = NSMakeRect(0, 0, [image size].width, [image size].height);
+ [image drawInRect:targetRect fromRect:sourceRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:YES hints:nil];
+
+ [context restoreGraphicsState];
+ textDisplacement = frame.size.height + 2;
+ }
+
+ NSRect textRect = NSMakeRect(
+ frame.origin.x + textDisplacement, frame.origin.y,
+ frame.size.width - textDisplacement, frame.size.height
+ );
+
+ NSColor *textColor = [self isHighlighted]
+ ? [NSColor alternateSelectedControlTextColor]
+ : [NSColor textColor];
+
+ [text drawInRect:textRect withAttributes:@{
+ NSForegroundColorAttributeName:textColor,
+ NSFontAttributeName:[self font]
+ }];
+}
+
+@end
+
+namespace phoenix {
+
+void pListView::append(const lstring &text) {
+ @autoreleasepool {
+ [[cocoaView content] reloadData];
+ }
+}
+
+void pListView::autoSizeColumns() {
+ @autoreleasepool {
+ if(listView.state.checkable) {
+ NSTableColumn *tableColumn = [[cocoaView content] tableColumnWithIdentifier:@"check"];
+ [tableColumn setWidth:20.0];
+ }
+
+ unsigned height = [[cocoaView content] rowHeight];
+ for(unsigned column = 0; column < listView.state.headerText.size(); column++) {
+ NSTableColumn *tableColumn = [[cocoaView content] tableColumnWithIdentifier:[[NSNumber numberWithInteger:column] stringValue]];
+ unsigned minimumWidth = pFont::size([[tableColumn headerCell] font], listView.state.headerText(column)).width + 4;
+ for(unsigned row = 0; row < listView.state.text.size(); row++) {
+ unsigned width = pFont::size([cocoaView font], listView.state.text(row)(column)).width + 2;
+ if(listView.state.image(row)(height).empty() == false) width += height + 2;
+ if(width > minimumWidth) minimumWidth = width;
+ }
+ [tableColumn setWidth:minimumWidth];
+ }
+ }
+}
+
+bool pListView::checked(unsigned row) {
+ return listView.state.checked(row);
+}
+
+void pListView::modify(unsigned row, const lstring &text) {
+ @autoreleasepool {
+ [[cocoaView content] reloadData];
+ }
+}
+
+void pListView::remove(unsigned row) {
+ @autoreleasepool {
+ [[cocoaView content] reloadData];
+ }
+}
+
+void pListView::reset() {
+ @autoreleasepool {
+ [[cocoaView content] reloadData];
+ }
+}
+
+bool pListView::selected() {
+ @autoreleasepool {
+ return [[cocoaView content] selectedRow] >= 0;
+ }
+}
+
+unsigned pListView::selection() {
+ if(selected() == false) return 0;
+
+ @autoreleasepool {
+ return [[cocoaView content] selectedRow];
+ }
+}
+
+void pListView::setCheckable(bool checkable) {
+ @autoreleasepool {
+ [cocoaView reloadColumns];
+ }
+}
+
+void pListView::setChecked(unsigned row, bool checked) {
+ @autoreleasepool {
+ [[cocoaView content] reloadData];
+ }
+}
+
+void pListView::setFont(const string &font) {
+ @autoreleasepool {
+ [cocoaView setFont:pFont::cocoaFont(font)];
+ }
+}
+
+void pListView::setHeaderText(const lstring &text) {
+ @autoreleasepool {
+ [cocoaView reloadColumns];
+ }
+}
+
+void pListView::setHeaderVisible(bool visible) {
+ @autoreleasepool {
+ if(visible) {
+ [[cocoaView content] setHeaderView:[[[NSTableHeaderView alloc] init] autorelease]];
+ } else {
+ [[cocoaView content] setHeaderView:nil];
+ }
+ }
+}
+
+void pListView::setImage(unsigned row, unsigned column, const image &image) {
+ @autoreleasepool {
+ [[cocoaView content] reloadData];
+ }
+}
+
+void pListView::setSelected(bool selected) {
+ @autoreleasepool {
+ if(selected == false) {
+ [[cocoaView content] deselectAll:nil];
+ }
+ }
+}
+
+void pListView::setSelection(unsigned row) {
+ @autoreleasepool {
+ [[cocoaView content] selectRowIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row, 1)] byExtendingSelection:NO];
+ }
+}
+
+void pListView::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaListView = [[CocoaListView alloc] initWith:listView];
+ setHeaderVisible(listView.state.headerVisible);
+ setHeaderText(listView.state.headerText);
+ }
+}
+
+void pListView::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/list-view.hpp b/higan/phoenix/cocoa/widget/list-view.hpp
new file mode 100644
index 00000000..5d794d3b
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/list-view.hpp
@@ -0,0 +1,62 @@
+@class CocoaListViewContent;
+
+@interface CocoaListView : NSScrollView {
+@public
+ phoenix::ListView *listView;
+ CocoaListViewContent *content;
+ NSFont *font;
+}
+-(id) initWith :(phoenix::ListView&)listView;
+-(void) dealloc;
+-(CocoaListViewContent*) content;
+-(NSFont*) font;
+-(void) setFont :(NSFont*)font;
+-(void) reloadColumns;
+-(NSInteger) numberOfRowsInTableView :(NSTableView*)table;
+-(id) tableView :(NSTableView*)table objectValueForTableColumn :(NSTableColumn*)tableColumn row:(NSInteger)row;
+-(void) tableView :(NSTableView*)table setObjectValue:(id)object forTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row;
+-(void) tableView :(NSTableView*)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row;
+-(void) tableViewSelectionDidChange :(NSNotification*)notification;
+-(IBAction) activate :(id)sender;
+@end
+
+@interface CocoaListViewContent : NSTableView {
+}
+-(void) keyDown :(NSEvent*)event;
+@end
+
+@interface CocoaListViewCell : NSTextFieldCell {
+}
+-(NSString*) stringValue;
+-(void) drawWithFrame :(NSRect)frame inView:(NSView*)view;
+@end
+
+namespace phoenix {
+
+struct pListView : public pWidget {
+ ListView &listView;
+ CocoaListView *cocoaListView;
+
+ void append(const lstring &text);
+ void autoSizeColumns();
+ bool checked(unsigned row);
+ void modify(unsigned row, const lstring &text);
+ void remove(unsigned row);
+ void reset();
+ bool selected();
+ unsigned selection();
+ void setCheckable(bool checkable);
+ void setChecked(unsigned row, bool checked);
+ void setFont(const string &font);
+ void setHeaderText(const lstring &text);
+ void setHeaderVisible(bool visible);
+ void setImage(unsigned row, unsigned column, const image &image);
+ void setSelected(bool selected);
+ void setSelection(unsigned row);
+
+ pListView(ListView &listView) : pWidget(listView), listView(listView) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/progress-bar.cpp b/higan/phoenix/cocoa/widget/progress-bar.cpp
new file mode 100644
index 00000000..6a4cad1e
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/progress-bar.cpp
@@ -0,0 +1,37 @@
+@implementation CocoaProgressBar : NSProgressIndicator
+
+-(id) initWith :(phoenix::ProgressBar&)progressBarReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0)]) {
+ progressBar = &progressBarReference;
+
+ [self setIndeterminate:NO];
+ [self setMinValue:0.0];
+ [self setMaxValue:100.0];
+ }
+ return self;
+}
+
+@end
+
+namespace phoenix {
+
+void pProgressBar::setPosition(unsigned position) {
+ @autoreleasepool {
+ [cocoaView setDoubleValue:position];
+ }
+}
+
+void pProgressBar::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaProgressBar = [[CocoaProgressBar alloc] initWith:progressBar];
+ setPosition(progressBar.state.position);
+ }
+}
+
+void pProgressBar::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/progress-bar.hpp b/higan/phoenix/cocoa/widget/progress-bar.hpp
new file mode 100644
index 00000000..7fd43079
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/progress-bar.hpp
@@ -0,0 +1,21 @@
+@interface CocoaProgressBar : NSProgressIndicator {
+@public
+ phoenix::ProgressBar *progressBar;
+}
+-(id) initWith :(phoenix::ProgressBar&)progressBar;
+@end
+
+namespace phoenix {
+
+struct pProgressBar : public pWidget {
+ ProgressBar &progressBar;
+ CocoaProgressBar *cocoaProgressBar;
+
+ void setPosition(unsigned position);
+
+ pProgressBar(ProgressBar &progressBar) : pWidget(progressBar), progressBar(progressBar) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/radio-button.cpp b/higan/phoenix/cocoa/widget/radio-button.cpp
new file mode 100644
index 00000000..77ba67e0
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/radio-button.cpp
@@ -0,0 +1,64 @@
+@implementation CocoaRadioButton : NSButton
+
+-(id) initWith :(phoenix::RadioButton&)radioButtonReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0)]) {
+ radioButton = &radioButtonReference;
+
+ [self setTarget:self];
+ [self setAction:@selector(activate:)];
+ [self setButtonType:NSRadioButton];
+ }
+ return self;
+}
+
+-(IBAction) activate :(id)sender {
+ radioButton->setChecked();
+ if(radioButton->onActivate) radioButton->onActivate();
+}
+
+@end
+
+namespace phoenix {
+
+bool pRadioButton::checked() {
+ @autoreleasepool {
+ return [cocoaView state] != NSOffState;
+ }
+}
+
+Size pRadioButton::minimumSize() {
+ Size size = Font::size(radioButton.font(), radioButton.state.text);
+ return {size.width + 24, size.height + 8};
+}
+
+void pRadioButton::setChecked() {
+ @autoreleasepool {
+ for(auto &button : radioButton.state.group) {
+ auto state = (&button == &radioButton) ? NSOnState : NSOffState;
+ [button.p.cocoaView setState:state];
+ }
+ }
+}
+
+void pRadioButton::setGroup(const set &group) {
+}
+
+void pRadioButton::setText(const string &text) {
+ @autoreleasepool {
+ [cocoaView setTitle:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pRadioButton::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaRadioButton = [[CocoaRadioButton alloc] initWith:radioButton];
+ }
+}
+
+void pRadioButton::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/radio-button.hpp b/higan/phoenix/cocoa/widget/radio-button.hpp
new file mode 100644
index 00000000..3c0a6658
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/radio-button.hpp
@@ -0,0 +1,25 @@
+@interface CocoaRadioButton : NSButton {
+@public
+ phoenix::RadioButton *radioButton;
+}
+-(id) initWith :(phoenix::RadioButton&)radioButton;
+@end
+
+namespace phoenix {
+
+struct pRadioButton : public pWidget {
+ RadioButton &radioButton;
+ CocoaRadioButton *cocoaRadioButton;
+
+ bool checked();
+ Size minimumSize();
+ void setChecked();
+ void setGroup(const set &group);
+ void setText(const string &text);
+
+ pRadioButton(RadioButton &radioButton) : pWidget(radioButton), radioButton(radioButton) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/sizable.hpp b/higan/phoenix/cocoa/widget/sizable.hpp
new file mode 100644
index 00000000..b7c2f934
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/sizable.hpp
@@ -0,0 +1,9 @@
+namespace phoenix {
+
+struct pSizable : public pObject {
+ Sizable &sizable;
+
+ pSizable(Sizable &sizable) : pObject(sizable), sizable(sizable) {}
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/text-edit.cpp b/higan/phoenix/cocoa/widget/text-edit.cpp
new file mode 100644
index 00000000..36dde2a9
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/text-edit.cpp
@@ -0,0 +1,101 @@
+@implementation CocoaTextEdit : NSScrollView
+
+-(id) initWith :(phoenix::TextEdit&)textEditReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0)]) {
+ textEdit = &textEditReference;
+
+ content = [[[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)] autorelease];
+ [content setDelegate:self];
+ [content setRichText:NO];
+
+ [self setBorderType:NSBezelBorder];
+ [self setDocumentView:content];
+ [self configure];
+ }
+ return self;
+}
+
+-(NSTextView*) content {
+ return content;
+}
+
+-(void) configure {
+ [content setMinSize:NSMakeSize(0, 0)];
+ [content setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+
+ [[content textContainer] setContainerSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+ [[content textContainer] setWidthTracksTextView:textEdit->wordWrap()];
+
+ [content setHorizontallyResizable:YES];
+ [content setVerticallyResizable:YES];
+ [content setAutoresizingMask:NSViewNotSizable];
+
+ [self setHasHorizontalScroller:!textEdit->wordWrap()];
+ [self setHasVerticalScroller:YES];
+}
+
+-(void) textDidChange :(NSNotification*)notification {
+ textEdit->state.text = [[content string] UTF8String];
+ if(textEdit->onChange) textEdit->onChange();
+}
+
+@end
+
+namespace phoenix {
+
+void pTextEdit::setCursorPosition(unsigned position) {
+ @autoreleasepool {
+ string text = [[[cocoaView content] string] UTF8String];
+ position = min(position, text.length());
+ [[cocoaView content] setSelectedRange:NSMakeRange(position, 0)];
+ }
+}
+
+void pTextEdit::setEditable(bool editable) {
+ @autoreleasepool {
+ [[cocoaView content] setEditable:editable];
+ }
+}
+
+void pTextEdit::setFont(const string &font) {
+ @autoreleasepool {
+ [[cocoaView content] setFont:pFont::cocoaFont(font)];
+ }
+}
+
+void pTextEdit::setText(const string &text) {
+ @autoreleasepool {
+ [[cocoaView content] setString:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pTextEdit::setWordWrap(bool wordWrap) {
+ @autoreleasepool {
+ [cocoaView configure];
+ }
+}
+
+string pTextEdit::text() {
+ @autoreleasepool {
+ return [[[cocoaView content] string] UTF8String];
+ }
+}
+
+void pTextEdit::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaTextEdit = [[CocoaTextEdit alloc] initWith:textEdit];
+ setEditable(textEdit.state.editable);
+ setWordWrap(textEdit.state.wordWrap);
+ setFont(textEdit.font());
+ setText(textEdit.state.text);
+ setCursorPosition(textEdit.state.cursorPosition);
+ }
+}
+
+void pTextEdit::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/text-edit.hpp b/higan/phoenix/cocoa/widget/text-edit.hpp
new file mode 100644
index 00000000..2b58f62a
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/text-edit.hpp
@@ -0,0 +1,30 @@
+@interface CocoaTextEdit : NSScrollView {
+@public
+ phoenix::TextEdit *textEdit;
+ NSTextView *content;
+}
+-(id) initWith :(phoenix::TextEdit&)textEdit;
+-(NSTextView*) content;
+-(void) configure;
+-(void) textDidChange :(NSNotification*)notification;
+@end
+
+namespace phoenix {
+
+struct pTextEdit : public pWidget {
+ TextEdit &textEdit;
+ CocoaTextEdit *cocoaTextEdit;
+
+ void setCursorPosition(unsigned position);
+ void setEditable(bool editable);
+ void setFont(const string &font);
+ void setText(const string &text);
+ void setWordWrap(bool wordWrap);
+ string text();
+
+ pTextEdit(TextEdit &textEdit) : pWidget(textEdit), textEdit(textEdit) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/vertical-scroller.cpp b/higan/phoenix/cocoa/widget/vertical-scroller.cpp
new file mode 100644
index 00000000..830eb52b
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/vertical-scroller.cpp
@@ -0,0 +1,91 @@
+@implementation CocoaVerticalScroller : NSScroller
+
+-(id) initWith :(phoenix::VerticalScroller&)verticalScrollerReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 1)]) {
+ verticalScroller = &verticalScrollerReference;
+
+ [self setTarget:self];
+ [self setAction:@selector(scroll:)];
+
+ [self setControlSize:NSRegularControlSize];
+ [self setScrollerStyle:NSScrollerStyleLegacy];
+ [self setEnabled:YES];
+
+ [self update];
+ }
+ return self;
+}
+
+-(void) update {
+ double d = 1.0 / verticalScroller->state.length;
+ double f = d * verticalScroller->state.position;
+
+ [self setDoubleValue:f];
+ [self setKnobProportion:d];
+}
+
+-(IBAction) scroll :(id)sender {
+ auto &state = verticalScroller->state;
+
+ switch([self hitPart]) {
+ case NSScrollerIncrementLine:
+ case NSScrollerIncrementPage:
+ if(state.position < state.length - 1) state.position++;
+ [self update];
+ break;
+
+ case NSScrollerDecrementLine:
+ case NSScrollerDecrementPage:
+ if(state.position) state.position--;
+ [self update];
+ break;
+
+ case NSScrollerKnob:
+ state.position = [self doubleValue] * state.length;
+ break;
+ }
+
+ if(verticalScroller->onChange) verticalScroller->onChange();
+}
+
+@end
+
+namespace phoenix {
+
+Size pVerticalScroller::minimumSize() {
+ @autoreleasepool {
+ return {[NSScroller scrollerWidthForControlSize:NSRegularControlSize scrollerStyle:NSScrollerStyleLegacy], 32};
+ }
+}
+
+unsigned pVerticalScroller::position() {
+ @autoreleasepool {
+ return [cocoaView doubleValue] * verticalScroller.state.length;
+ }
+}
+
+void pVerticalScroller::setLength(unsigned length) {
+ @autoreleasepool {
+ [cocoaView update];
+ }
+}
+
+void pVerticalScroller::setPosition(unsigned position) {
+ @autoreleasepool {
+ [cocoaView update];
+ }
+}
+
+void pVerticalScroller::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaVerticalScroller = [[CocoaVerticalScroller alloc] initWith:verticalScroller];
+ }
+}
+
+void pVerticalScroller::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/vertical-scroller.hpp b/higan/phoenix/cocoa/widget/vertical-scroller.hpp
new file mode 100644
index 00000000..ad2d87aa
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/vertical-scroller.hpp
@@ -0,0 +1,24 @@
+@interface CocoaVerticalScroller : NSScroller {
+@public
+ phoenix::VerticalScroller *verticalScroller;
+}
+-(id) initWith :(phoenix::VerticalScroller&)verticalScroller;
+@end
+
+namespace phoenix {
+
+struct pVerticalScroller : public pWidget {
+ VerticalScroller &verticalScroller;
+ CocoaVerticalScroller *cocoaVerticalScroller;
+
+ Size minimumSize();
+ unsigned position();
+ void setLength(unsigned length);
+ void setPosition(unsigned position);
+
+ pVerticalScroller(VerticalScroller &verticalScroller) : pWidget(verticalScroller), verticalScroller(verticalScroller) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/vertical-slider.cpp b/higan/phoenix/cocoa/widget/vertical-slider.cpp
new file mode 100644
index 00000000..dadd07f9
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/vertical-slider.cpp
@@ -0,0 +1,60 @@
+@implementation CocoaVerticalSlider : NSSlider
+
+-(id) initWith :(phoenix::VerticalSlider&)verticalSliderReference {
+ if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 1)]) {
+ verticalSlider = &verticalSliderReference;
+
+ [self setTarget:self];
+ [self setAction:@selector(activate:)];
+ [self setMinValue:0];
+ }
+ return self;
+}
+
+-(IBAction) activate :(id)sender {
+ verticalSlider->state.position = [self doubleValue];
+ if(verticalSlider->onChange) verticalSlider->onChange();
+}
+
+@end
+
+namespace phoenix {
+
+Size pVerticalSlider::minimumSize() {
+ return {24, 64};
+}
+
+unsigned pVerticalSlider::position() {
+ @autoreleasepool {
+ return [cocoaView doubleValue];
+ }
+}
+
+void pVerticalSlider::setLength(unsigned length) {
+ @autoreleasepool {
+ [cocoaView setMaxValue:length];
+ }
+}
+
+void pVerticalSlider::setPosition(unsigned position) {
+ @autoreleasepool {
+ [cocoaView setDoubleValue:position];
+ }
+}
+
+void pVerticalSlider::constructor() {
+ @autoreleasepool {
+ cocoaView = cocoaVerticalSlider = [[CocoaVerticalSlider alloc] initWith:verticalSlider];
+
+ setLength(verticalSlider.state.length);
+ setPosition(verticalSlider.state.position);
+ }
+}
+
+void pVerticalSlider::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/vertical-slider.hpp b/higan/phoenix/cocoa/widget/vertical-slider.hpp
new file mode 100644
index 00000000..dfcb8e69
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/vertical-slider.hpp
@@ -0,0 +1,25 @@
+@interface CocoaVerticalSlider : NSSlider {
+@public
+ phoenix::VerticalSlider *verticalSlider;
+}
+-(id) initWith :(phoenix::VerticalSlider&)verticalSlider;
+-(IBAction) activate :(id)sender;
+@end
+
+namespace phoenix {
+
+struct pVerticalSlider : public pWidget {
+ VerticalSlider &verticalSlider;
+ CocoaVerticalSlider *cocoaVerticalSlider;
+
+ Size minimumSize();
+ unsigned position();
+ void setLength(unsigned length);
+ void setPosition(unsigned position);
+
+ pVerticalSlider(VerticalSlider &verticalSlider) : pWidget(verticalSlider), verticalSlider(verticalSlider) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/viewport.cpp b/higan/phoenix/cocoa/widget/viewport.cpp
new file mode 100644
index 00000000..5d088e3d
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/viewport.cpp
@@ -0,0 +1,10 @@
+namespace phoenix {
+
+uintptr_t pViewport::handle() {
+ return 0;
+}
+
+void pViewport::constructor() {
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/viewport.hpp b/higan/phoenix/cocoa/widget/viewport.hpp
new file mode 100644
index 00000000..94a6eecb
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/viewport.hpp
@@ -0,0 +1,12 @@
+namespace phoenix {
+
+struct pViewport : public pWidget {
+ Viewport &viewport;
+
+ uintptr_t handle();
+
+ pViewport(Viewport &viewport) : pWidget(viewport), viewport(viewport) {}
+ void constructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/widget/widget.cpp b/higan/phoenix/cocoa/widget/widget.cpp
new file mode 100644
index 00000000..6c1b75ee
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/widget.cpp
@@ -0,0 +1,76 @@
+namespace phoenix {
+
+bool pWidget::enabled() {
+ @autoreleasepool {
+ return [cocoaView respondsToSelector:@selector(enabled)] && [cocoaView enabled];
+ }
+}
+
+bool pWidget::focused() {
+ @autoreleasepool {
+ return cocoaView == [[cocoaView window] firstResponder];
+ }
+}
+
+Size pWidget::minimumSize() {
+ return {0, 0};
+}
+
+void pWidget::setEnabled(bool enabled) {
+ if(widget.state.abstract) enabled = false;
+ if(sizable.state.layout && sizable.state.layout->enabled() == false) enabled = false;
+
+ @autoreleasepool {
+ if([cocoaView respondsToSelector:@selector(setEnabled:)]) {
+ [cocoaView setEnabled:enabled];
+ }
+ }
+}
+
+void pWidget::setFocused() {
+ @autoreleasepool {
+ [[cocoaView window] makeFirstResponder:cocoaView];
+ }
+}
+
+void pWidget::setFont(const string &font) {
+ @autoreleasepool {
+ if([cocoaView respondsToSelector:@selector(setFont:)]) {
+ [cocoaView setFont:pFont::cocoaFont(font)];
+ }
+ }
+}
+
+void pWidget::setGeometry(const Geometry &geometry) {
+ @autoreleasepool {
+ CGFloat windowHeight = [[cocoaView superview] frame].size.height;
+ [cocoaView setFrame:NSMakeRect(geometry.x, windowHeight - geometry.y - geometry.height, geometry.width, geometry.height)];
+ [[cocoaView superview] setNeedsDisplay:YES];
+ }
+}
+
+void pWidget::setVisible(bool visible) {
+ if(widget.state.abstract) visible = false;
+ if(sizable.state.layout && sizable.state.layout->visible() == false) visible = false;
+
+ @autoreleasepool {
+ [cocoaView setHidden:!visible];
+ }
+}
+
+void pWidget::constructor() {
+ if(!widget.state.abstract) return;
+
+ @autoreleasepool {
+ cocoaView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)];
+ [cocoaView setHidden:true];
+ }
+}
+
+void pWidget::destructor() {
+ @autoreleasepool {
+ [cocoaView release];
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/widget/widget.hpp b/higan/phoenix/cocoa/widget/widget.hpp
new file mode 100644
index 00000000..d0dc99a2
--- /dev/null
+++ b/higan/phoenix/cocoa/widget/widget.hpp
@@ -0,0 +1,21 @@
+namespace phoenix {
+
+struct pWidget : public pSizable {
+ Widget &widget;
+ NSView *cocoaView;
+
+ bool enabled();
+ bool focused();
+ virtual Size minimumSize();
+ void setEnabled(bool enabled);
+ void setFocused();
+ virtual void setFont(const string &font);
+ void setGeometry(const Geometry &geometry);
+ void setVisible(bool visible);
+
+ pWidget(Widget &widget) : pSizable(widget), widget(widget) {}
+ void constructor();
+ void destructor();
+};
+
+}
diff --git a/higan/phoenix/cocoa/window.cpp b/higan/phoenix/cocoa/window.cpp
new file mode 100644
index 00000000..cb920b94
--- /dev/null
+++ b/higan/phoenix/cocoa/window.cpp
@@ -0,0 +1,326 @@
+@implementation CocoaWindow : NSWindow
+
+-(id) initWith :(phoenix::Window&)windowReference {
+ window = &windowReference;
+
+ NSUInteger style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;
+ if(window->state.resizable) style |= NSResizableWindowMask;
+
+ if(self = [super initWithContentRect:NSMakeRect(0, 0, 640, 480) styleMask:style backing:NSBackingStoreBuffered defer:YES]) {
+ [self setDelegate:self];
+ [self setReleasedWhenClosed:NO];
+ [self setAcceptsMouseMovedEvents:YES];
+ [self setLevel:NSFloatingWindowLevel]; //when launched from a terminal, this places the window above it
+ [self setTitle:@""];
+
+ menu = [[NSMenu alloc] init];
+ [menu retain];
+
+ NSMenuItem *item;
+ string text;
+
+ rootMenu = [[NSMenu alloc] init];
+ item = [[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""] autorelease];
+ [item setSubmenu:rootMenu];
+ [menu addItem:item];
+
+ text = {"About ", phoenix::applicationState.name, "..."};
+ item = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:text] action:@selector(menuAbout) keyEquivalent:@""] autorelease];
+ [rootMenu addItem:item];
+ [rootMenu addItem:[NSMenuItem separatorItem]];
+
+ item = [[[NSMenuItem alloc] initWithTitle:@"Preferences" action:@selector(menuPreferences) keyEquivalent:@""] autorelease];
+ [rootMenu addItem:item];
+ [rootMenu addItem:[NSMenuItem separatorItem]];
+
+ text = {"Quit ", phoenix::applicationState.name};
+ item = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:text] action:@selector(menuQuit) keyEquivalent:@""] autorelease];
+ [rootMenu addItem:item];
+ }
+
+ return self;
+}
+
+-(BOOL) canBecomeKeyWindow {
+ return YES;
+}
+
+-(BOOL) canBecomeMainWindow {
+ return YES;
+}
+
+-(void) windowDidBecomeMain :(NSNotification*)notification {
+ if(window->state.menu.size() > 0) {
+ [NSApp setMainMenu:menu];
+ }
+}
+
+-(void) windowDidMove :(NSNotification*)notification {
+ window->p.moveEvent();
+}
+
+-(void) windowDidResize :(NSNotification*)notification {
+ window->p.sizeEvent();
+}
+
+-(BOOL) windowShouldClose :(id)sender {
+ if(window->onClose) window->onClose();
+ else window->setVisible(false);
+ if(window->state.modal && !window->visible()) window->setModal(false);
+ return NO;
+}
+
+-(NSMenu*) menu {
+ return menu;
+}
+
+-(void) menuAbout {
+ using phoenix::Application;
+ if(Application::Cocoa::onAbout) Application::Cocoa::onAbout();
+}
+
+-(void) menuPreferences {
+ using phoenix::Application;
+ if(Application::Cocoa::onPreferences) Application::Cocoa::onPreferences();
+}
+
+-(void) menuQuit {
+ using phoenix::Application;
+ if(Application::Cocoa::onQuit) Application::Cocoa::onQuit();
+}
+
+@end
+
+namespace phoenix {
+
+Window& pWindow::none() {
+ static Window *window = nullptr;
+ if(window == nullptr) window = new Window;
+ return *window;
+}
+
+void pWindow::append(Layout &layout) {
+ Geometry geometry = window.state.geometry;
+ geometry.x = geometry.y = 0;
+ layout.setGeometry(geometry);
+}
+
+void pWindow::append(Menu &menu) {
+ @autoreleasepool {
+ [[cocoaWindow menu] addItem:menu.p.cocoaAction];
+ }
+}
+
+void pWindow::append(Widget &widget) {
+ if(widget.font().empty() && !window.state.widgetFont.empty()) {
+ widget.setFont(window.state.widgetFont);
+ }
+
+ @autoreleasepool {
+ [widget.p.cocoaView removeFromSuperview];
+ [[cocoaWindow contentView] addSubview:widget.p.cocoaView positioned:NSWindowAbove relativeTo:nil];
+ widget.p.setGeometry(widget.geometry());
+ [[cocoaWindow contentView] setNeedsDisplay:YES];
+ }
+}
+
+Color pWindow::backgroundColor() {
+ @autoreleasepool {
+ NSColor *color = [[cocoaWindow backgroundColor] colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
+ return {
+ uint8_t(255 * [color redComponent]),
+ uint8_t(255 * [color greenComponent]),
+ uint8_t(255 * [color blueComponent]),
+ uint8_t(255 * [color alphaComponent])
+ };
+ }
+}
+
+bool pWindow::focused() {
+ @autoreleasepool {
+ return [cocoaWindow isMainWindow] == YES;
+ }
+}
+
+Geometry pWindow::frameMargin() {
+ @autoreleasepool {
+ NSRect frame = [cocoaWindow frameRectForContentRect:NSMakeRect(0, 0, 640, 480)];
+ return {abs(frame.origin.x), frame.size.height - 480, frame.size.width - 640, abs(frame.origin.y)};
+ }
+}
+
+Geometry pWindow::geometry() {
+ @autoreleasepool {
+ NSRect area = [cocoaWindow contentRectForFrameRect:[cocoaWindow frame]];
+ return {area.origin.x, Desktop::size().height - area.origin.y - area.size.height, area.size.width, area.size.height};
+ }
+}
+
+void pWindow::remove(Layout &layout) {
+ @autoreleasepool {
+ [[cocoaWindow contentView] setNeedsDisplay:YES];
+ }
+}
+
+void pWindow::remove(Menu &menu) {
+ @autoreleasepool {
+ [[cocoaWindow menu] removeItem:menu.p.cocoaAction];
+ }
+}
+
+void pWindow::remove(Widget &widget) {
+ @autoreleasepool {
+ [widget.p.cocoaView removeFromSuperview];
+ [[cocoaWindow contentView] setNeedsDisplay:YES];
+ }
+}
+
+void pWindow::setBackgroundColor(const Color &color) {
+ @autoreleasepool {
+ [cocoaWindow
+ setBackgroundColor:[NSColor
+ colorWithCalibratedRed:color.red / 255.0
+ green:color.green / 255.0
+ blue:color.blue / 255.0
+ alpha:color.alpha / 255.0
+ ]
+ ];
+ }
+}
+
+void pWindow::setFocused() {
+ @autoreleasepool {
+ [cocoaWindow makeKeyAndOrderFront:nil];
+ }
+}
+
+void pWindow::setFullScreen(bool fullScreen) {
+ @autoreleasepool {
+ [cocoaWindow setLevel:NSNormalWindowLevel];
+ if(fullScreen == true) {
+ [NSApp setPresentationOptions:NSApplicationPresentationFullScreen];
+ [cocoaWindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+ [cocoaWindow toggleFullScreen:nil];
+ } else {
+ [cocoaWindow toggleFullScreen:nil];
+ [cocoaWindow setCollectionBehavior:NSWindowCollectionBehaviorDefault];
+ [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+ }
+ }
+}
+
+void pWindow::setGeometry(const Geometry &geometry) {
+ locked = true;
+
+ @autoreleasepool {
+ [cocoaWindow
+ setFrame:[cocoaWindow
+ frameRectForContentRect:NSMakeRect(geometry.x, Desktop::size().height - geometry.y - geometry.height, geometry.width, geometry.height)
+ ]
+ display:YES
+ ];
+
+ for(auto &layout : window.state.layout) {
+ Geometry geometry = this->geometry();
+ geometry.x = geometry.y = 0;
+ layout.setGeometry(geometry);
+ }
+ }
+
+ locked = false;
+}
+
+void pWindow::setMenuFont(const string &font) {
+}
+
+void pWindow::setMenuVisible(bool visible) {
+}
+
+void pWindow::setModal(bool modal) {
+ @autoreleasepool {
+ if(modal == true) {
+ [NSApp runModalForWindow:cocoaWindow];
+ } else {
+ [NSApp stopModal];
+ NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0.0 windowNumber:0 context:nil subtype:0 data1:0 data2:0];
+ [NSApp postEvent:event atStart:true];
+ }
+ }
+}
+
+void pWindow::setResizable(bool resizable) {
+ @autoreleasepool {
+ NSUInteger style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;
+ if(resizable) style |= NSResizableWindowMask;
+ [cocoaWindow setStyleMask:style];
+ }
+}
+
+void pWindow::setStatusFont(const string &font) {
+}
+
+void pWindow::setStatusText(const string &text) {
+}
+
+void pWindow::setStatusVisible(bool visible) {
+}
+
+void pWindow::setTitle(const string &text) {
+ @autoreleasepool {
+ [cocoaWindow setTitle:[NSString stringWithUTF8String:text]];
+ }
+}
+
+void pWindow::setVisible(bool visible) {
+ @autoreleasepool {
+ if(visible) [cocoaWindow makeKeyAndOrderFront:nil];
+ else [cocoaWindow orderOut:nil];
+ }
+}
+
+void pWindow::setWidgetFont(const string &font) {
+}
+
+void pWindow::constructor() {
+ @autoreleasepool {
+ cocoaWindow = [[CocoaWindow alloc] initWith:window];
+ }
+}
+
+void pWindow::destructor() {
+ @autoreleasepool {
+ [cocoaWindow release];
+ }
+}
+
+void pWindow::moveEvent() {
+ if(locked == false && window.fullScreen() == false && window.visible() == true) {
+ Geometry geometry = this->geometry();
+ window.state.geometry.x = geometry.x;
+ window.state.geometry.y = geometry.y;
+ }
+
+ if(locked == false) {
+ if(window.onMove) window.onMove();
+ }
+}
+
+void pWindow::sizeEvent() {
+ if(locked == false && window.fullScreen() == false && window.visible() == true) {
+ Geometry geometry = this->geometry();
+ window.state.geometry.width = geometry.width;
+ window.state.geometry.height = geometry.height;
+ }
+
+ for(auto &layout : window.state.layout) {
+ Geometry geometry = this->geometry();
+ geometry.x = geometry.y = 0;
+ layout.setGeometry(geometry);
+ }
+
+ if(locked == false) {
+ if(window.onSize) window.onSize();
+ }
+}
+
+}
diff --git a/higan/phoenix/cocoa/window.hpp b/higan/phoenix/cocoa/window.hpp
new file mode 100644
index 00000000..79e9407e
--- /dev/null
+++ b/higan/phoenix/cocoa/window.hpp
@@ -0,0 +1,60 @@
+@interface CocoaWindow : NSWindow {
+@public
+ phoenix::Window *window;
+ NSMenu *menu;
+ NSMenu *rootMenu;
+}
+-(id) initWith :(phoenix::Window&)window;
+-(BOOL) canBecomeKeyWindow;
+-(BOOL) canBecomeMainWindow;
+-(void) windowDidBecomeMain :(NSNotification*)notification;
+-(void) windowDidMove :(NSNotification*)notification;
+-(void) windowDidResize :(NSNotification*)notification;
+-(BOOL) windowShouldClose :(id)sender;
+-(NSMenu*) menu;
+-(void) menuAbout;
+-(void) menuPreferences;
+-(void) menuQuit;
+@end
+
+namespace phoenix {
+
+struct pWindow : public pObject {
+ Window &window;
+ CocoaWindow *cocoaWindow;
+
+ static Window& none();
+
+ void append(Layout &layout);
+ void append(Menu &menu);
+ void append(Widget &widget);
+ Color backgroundColor();
+ bool focused();
+ Geometry frameMargin();
+ Geometry geometry();
+ void remove(Layout &layout);
+ void remove(Menu &menu);
+ void remove(Widget &widget);
+ void setBackgroundColor(const Color &color);
+ void setFocused();
+ void setFullScreen(bool fullScreen);
+ void setGeometry(const Geometry &geometry);
+ void setMenuFont(const string &font);
+ void setMenuVisible(bool visible);
+ void setModal(bool modal);
+ void setResizable(bool resizable);
+ void setStatusFont(const string &font);
+ void setStatusText(const string &text);
+ void setStatusVisible(bool visible);
+ void setTitle(const string &text);
+ void setVisible(bool visible);
+ void setWidgetFont(const string &font);
+
+ pWindow(Window &window) : pObject(window), window(window) {}
+ void constructor();
+ void destructor();
+ void moveEvent();
+ void sizeEvent();
+};
+
+}
diff --git a/higan/phoenix/core/core.cpp b/higan/phoenix/core/core.cpp
index a2588770..2bd5f4bd 100755
--- a/higan/phoenix/core/core.cpp
+++ b/higan/phoenix/core/core.cpp
@@ -1,7 +1,24 @@
-#include "state.hpp"
-#include "layout/fixed-layout.cpp"
-#include "layout/horizontal-layout.cpp"
-#include "layout/vertical-layout.cpp"
+#if defined(PHOENIX_WINDOWS)
+ #include "../windows/header.hpp"
+#elif defined(PHOENIX_QT)
+ #include "../qt/header.hpp"
+#elif defined(PHOENIX_GTK)
+ #include "../gtk/header.hpp"
+#elif defined(PHOENIX_COCOA)
+ #include "../cocoa/header.hpp"
+#elif defined(PHOENIX_REFERENCE)
+ #include "../reference/header.hpp"
+#endif
+
+#include "core.hpp"
+using namespace nall;
+
+namespace phoenix {
+ #include "state.hpp"
+ #include "layout/fixed-layout.cpp"
+ #include "layout/horizontal-layout.cpp"
+ #include "layout/vertical-layout.cpp"
+}
#if defined(PHOENIX_WINDOWS)
#include "../windows/platform.cpp"
@@ -9,11 +26,51 @@
#include "../qt/platform.cpp"
#elif defined(PHOENIX_GTK)
#include "../gtk/platform.cpp"
+#elif defined(PHOENIX_COCOA)
+ #include "../cocoa/platform.cpp"
#elif defined(PHOENIX_REFERENCE)
#include "../reference/platform.cpp"
#endif
-static bool OS_quit = false;
+namespace phoenix {
+
+//Application
+//===========
+
+nall::function Application::main;
+
+nall::function Application::Cocoa::onAbout;
+nall::function Application::Cocoa::onPreferences;
+nall::function Application::Cocoa::onQuit;
+
+void Application::run() {
+ return pApplication::run();
+}
+
+bool Application::pendingEvents() {
+ return pApplication::pendingEvents();
+}
+
+void Application::processEvents() {
+ return pApplication::processEvents();
+}
+
+void Application::quit() {
+ applicationState.quit = true;
+ return pApplication::quit();
+}
+
+void Application::setName(const string &name) {
+ applicationState.name = name;
+}
+
+void Application::initialize() {
+ static bool initialized = false;
+ if(initialized == false) {
+ initialized = true;
+ return pApplication::initialize();
+ }
+}
//Color
//=====
@@ -30,15 +87,15 @@ uint32_t Color::rgba() const {
//========
Position Geometry::position() const {
- return { x, y };
+ return {x, y};
}
Size Geometry::size() const {
- return { width, height };
+ return {width, height};
}
string Geometry::text() const {
- return { x, ",", y, ",", width, ",", height };
+ return {x, ",", y, ",", width, ",", height};
}
Geometry::Geometry(const string &text) {
@@ -52,12 +109,20 @@ Geometry::Geometry(const string &text) {
//Font
//====
-Geometry Font::geometry(const string &text) {
- return pFont::geometry(description, text);
+string Font::serif(unsigned size, const string &style) {
+ return pFont::serif(size, style);
}
-Font::Font(const string &description):
-description(description) {
+string Font::sans(unsigned size, const string &style) {
+ return pFont::sans(size, style);
+}
+
+string Font::monospace(unsigned size, const string &style) {
+ return pFont::monospace(size, style);
+}
+
+Size Font::size(const string &font, const string &text) {
+ return pFont::size(font, text);
}
//Desktop
@@ -144,7 +209,7 @@ MessageWindow::Response MessageWindow::critical(Window &parent, const string &te
Object::Object(pObject &p):
p(p) {
- OS::initialize();
+ Application::initialize();
p.constructor();
}
@@ -153,38 +218,6 @@ Object::~Object() {
delete &p;
}
-//OS
-//==
-
-void OS::main() {
- return pOS::main();
-}
-
-bool OS::pendingEvents() {
- return pOS::pendingEvents();
-}
-
-void OS::processEvents() {
- return pOS::processEvents();
-}
-
-void OS::quit() {
- OS_quit = true;
- return pOS::quit();
-}
-
-void OS::setName(const string &name) {
- osState.name = name;
-}
-
-void OS::initialize() {
- static bool initialized = false;
- if(initialized == false) {
- initialized = true;
- return pOS::initialize();
- }
-}
-
//Timer
//=====
@@ -221,7 +254,7 @@ Window& Window::none() {
void Window::append_(Layout &layout) {
if(state.layout.append(layout)) {
((Sizable&)layout).state.window = this;
- ((Sizable&)layout).state.layout = 0;
+ ((Sizable&)layout).state.layout = nullptr;
p.append(layout);
layout.synchronizeLayout();
}
@@ -270,28 +303,24 @@ Geometry Window::geometry() {
return p.geometry();
}
-void Window::ignore() {
- state.ignore = true;
-}
-
void Window::remove_(Layout &layout) {
if(state.layout.remove(layout)) {
p.remove(layout);
- ((Sizable&)layout).state.window = 0;
+ ((Sizable&)layout).state.window = nullptr;
}
}
void Window::remove_(Menu &menu) {
if(state.menu.remove(menu)) {
p.remove(menu);
- ((Action&)menu).state.window = 0;
+ ((Action&)menu).state.window = nullptr;
}
}
void Window::remove_(Widget &widget) {
if(state.widget.remove(widget)) {
p.remove(widget);
- ((Sizable&)widget).state.window = 0;
+ ((Sizable&)widget).state.window = nullptr;
}
}
@@ -387,7 +416,7 @@ string Window::statusText() {
}
void Window::synchronizeLayout() {
- if(visible() && OS_quit == false) setGeometry(geometry());
+ if(visible() && applicationState.quit == false) setGeometry(geometry());
}
bool Window::visible() {
@@ -455,7 +484,7 @@ void Menu::append(const set &list) {
void Menu::remove(const set &list) {
for(auto &action : list) {
if(state.action.remove(action)) {
- action.state.menu = 0;
+ action.state.menu = nullptr;
return p.remove(action);
}
}
@@ -627,7 +656,7 @@ Sizable::~Sizable() {
void Layout::append(Sizable &sizable) {
sizable.state.layout = this;
- sizable.state.window = 0;
+ sizable.state.window = nullptr;
if(dynamic_cast(&sizable)) {
Layout &layout = (Layout&)sizable;
@@ -646,8 +675,8 @@ void Layout::remove(Sizable &sizable) {
if(sizable.window()) sizable.window()->remove(widget);
}
- sizable.state.layout = 0;
- sizable.state.window = 0;
+ sizable.state.layout = nullptr;
+ sizable.state.window = nullptr;
}
Layout::Layout():
@@ -690,8 +719,8 @@ Geometry Widget::geometry() {
return state.geometry;
}
-Geometry Widget::minimumGeometry() {
- return p.minimumGeometry();
+Size Widget::minimumSize() {
+ return p.minimumSize();
}
void Widget::setEnabled(bool enabled) {
@@ -818,88 +847,88 @@ Canvas::~Canvas() {
delete &state;
}
-//CheckBox
-//========
+//CheckButton
+//===========
-bool CheckBox::checked() {
+bool CheckButton::checked() {
return p.checked();
}
-void CheckBox::setChecked(bool checked) {
+void CheckButton::setChecked(bool checked) {
state.checked = checked;
return p.setChecked(checked);
}
-void CheckBox::setText(const string &text) {
+void CheckButton::setText(const string &text) {
state.text = text;
return p.setText(text);
}
-CheckBox::CheckBox():
+CheckButton::CheckButton():
state(*new State),
-base_from_member(*new pCheckBox(*this)),
-Widget(base_from_member::value),
-p(base_from_member::value) {
+base_from_member(*new pCheckButton(*this)),
+Widget(base_from_member::value),
+p(base_from_member::value) {
p.constructor();
}
-CheckBox::~CheckBox() {
+CheckButton::~CheckButton() {
p.destructor();
delete &state;
}
-//ComboBox
-//========
+//ComboButton
+//===========
-void ComboBox::append_(const lstring &list) {
+void ComboButton::append_(const lstring &list) {
for(auto &text : list) {
state.text.append(text);
p.append(text);
}
}
-void ComboBox::modify(unsigned row, const string &text) {
+void ComboButton::modify(unsigned row, const string &text) {
state.text(row) = text;
p.modify(row, text);
}
-void ComboBox::remove(unsigned row) {
+void ComboButton::remove(unsigned row) {
state.text.remove(row);
p.remove(row);
}
-void ComboBox::reset() {
+void ComboButton::reset() {
state.selection = 0;
state.text.reset();
return p.reset();
}
-unsigned ComboBox::selection() {
+unsigned ComboButton::selection() {
return p.selection();
}
-void ComboBox::setSelection(unsigned row) {
+void ComboButton::setSelection(unsigned row) {
state.selection = row;
return p.setSelection(row);
}
-string ComboBox::text() {
+string ComboButton::text() {
return state.text(selection());
}
-string ComboBox::text(unsigned row) {
+string ComboButton::text(unsigned row) {
return state.text(row);
}
-ComboBox::ComboBox():
+ComboButton::ComboButton():
state(*new State),
-base_from_member(*new pComboBox(*this)),
-Widget(base_from_member::value),
-p(base_from_member::value) {
+base_from_member(*new pComboButton(*this)),
+Widget(base_from_member::value),
+p(base_from_member::value) {
p.constructor();
}
-ComboBox::~ComboBox() {
+ComboButton::~ComboButton() {
p.destructor();
delete &state;
}
@@ -944,36 +973,36 @@ HexEdit::~HexEdit() {
delete &state;
}
-//HorizontalScrollBar
-//===================
+//HorizontalScroller
+//==================
-unsigned HorizontalScrollBar::length() {
+unsigned HorizontalScroller::length() {
return state.length;
}
-unsigned HorizontalScrollBar::position() {
+unsigned HorizontalScroller::position() {
return p.position();
}
-void HorizontalScrollBar::setLength(unsigned length) {
+void HorizontalScroller::setLength(unsigned length) {
state.length = length;
return p.setLength(length);
}
-void HorizontalScrollBar::setPosition(unsigned position) {
+void HorizontalScroller::setPosition(unsigned position) {
state.position = position;
return p.setPosition(position);
}
-HorizontalScrollBar::HorizontalScrollBar():
+HorizontalScroller::HorizontalScroller():
state(*new State),
-base_from_member(*new pHorizontalScrollBar(*this)),
-Widget(base_from_member::value),
-p(base_from_member::value) {
+base_from_member(*new pHorizontalScroller(*this)),
+Widget(base_from_member::value),
+p(base_from_member::value) {
p.constructor();
}
-HorizontalScrollBar::~HorizontalScrollBar() {
+HorizontalScroller::~HorizontalScroller() {
p.destructor();
delete &state;
}
@@ -1176,38 +1205,38 @@ ProgressBar::~ProgressBar() {
delete &state;
}
-//RadioBox
-//========
+//RadioButton
+//===========
-void RadioBox::group(const set &list) {
+void RadioButton::group(const set &list) {
for(auto &item : list) item.p.setGroup(item.state.group = list);
if(list.size()) list[0].setChecked();
}
-bool RadioBox::checked() {
+bool RadioButton::checked() {
return p.checked();
}
-void RadioBox::setChecked() {
+void RadioButton::setChecked() {
for(auto &item : state.group) item.state.checked = false;
state.checked = true;
return p.setChecked();
}
-void RadioBox::setText(const string &text) {
+void RadioButton::setText(const string &text) {
state.text = text;
return p.setText(text);
}
-RadioBox::RadioBox():
+RadioButton::RadioButton():
state(*new State),
-base_from_member(*new pRadioBox(*this)),
-Widget(base_from_member::value),
-p(base_from_member::value) {
+base_from_member(*new pRadioButton(*this)),
+Widget(base_from_member::value),
+p(base_from_member::value) {
p.constructor();
}
-RadioBox::~RadioBox() {
+RadioButton::~RadioButton() {
for(auto &item : state.group) {
if(&item != this) item.state.group.remove(*this);
}
@@ -1242,6 +1271,10 @@ string TextEdit::text() {
return p.text();
}
+bool TextEdit::wordWrap() {
+ return state.wordWrap;
+}
+
TextEdit::TextEdit():
state(*new State),
base_from_member(*new pTextEdit(*this)),
@@ -1255,36 +1288,36 @@ TextEdit::~TextEdit() {
delete &state;
}
-//VerticalScrollBar
-//=================
+//VerticalScroller
+//================
-unsigned VerticalScrollBar::length() {
+unsigned VerticalScroller::length() {
return state.length;
}
-unsigned VerticalScrollBar::position() {
+unsigned VerticalScroller::position() {
return p.position();
}
-void VerticalScrollBar::setLength(unsigned length) {
+void VerticalScroller::setLength(unsigned length) {
state.length = length;
return p.setLength(length);
}
-void VerticalScrollBar::setPosition(unsigned position) {
+void VerticalScroller::setPosition(unsigned position) {
state.position = position;
return p.setPosition(position);
}
-VerticalScrollBar::VerticalScrollBar():
+VerticalScroller::VerticalScroller():
state(*new State),
-base_from_member(*new pVerticalScrollBar(*this)),
-Widget(base_from_member::value),
-p(base_from_member::value) {
+base_from_member(*new pVerticalScroller(*this)),
+Widget(base_from_member::value),
+p(base_from_member::value) {
p.constructor();
}
-VerticalScrollBar::~VerticalScrollBar() {
+VerticalScroller::~VerticalScroller() {
p.destructor();
delete &state;
}
@@ -1340,3 +1373,5 @@ p(base_from_member::value) {
Viewport::~Viewport() {
p.destructor();
}
+
+}
diff --git a/higan/phoenix/core/core.hpp b/higan/phoenix/core/core.hpp
index 1b329f85..f3c65f13 100755
--- a/higan/phoenix/core/core.hpp
+++ b/higan/phoenix/core/core.hpp
@@ -1,3 +1,17 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace phoenix {
+
+struct Application;
struct Font;
struct Window;
struct Menu;
@@ -5,9 +19,9 @@ struct Sizable;
struct Layout;
struct Widget;
+struct pApplication;
struct pFont;
struct pObject;
-struct pOS;
struct pTimer;
struct pWindow;
struct pAction;
@@ -21,21 +35,43 @@ struct pLayout;
struct pWidget;
struct pButton;
struct pCanvas;
-struct pCheckBox;
-struct pComboBox;
+struct pCheckButton;
+struct pComboButton;
struct pHexEdit;
-struct pHorizontalScrollBar;
+struct pHorizontalScroller;
struct pHorizontalSlider;
struct pLabel;
struct pLineEdit;
struct pListView;
struct pProgressBar;
-struct pRadioBox;
+struct pRadioButton;
struct pTextEdit;
-struct pVerticalScrollBar;
+struct pVerticalScroller;
struct pVerticalSlider;
struct pViewport;
+struct Application {
+ static nall::function main;
+
+ static void run();
+ static bool pendingEvents();
+ static void processEvents();
+ static void quit();
+ static void setName(const nall::string &name);
+
+ Application() = delete;
+ struct State;
+ static void initialize();
+
+ struct Cocoa {
+ static nall::function onAbout;
+ static nall::function onPreferences;
+ static nall::function onQuit;
+ };
+};
+
+typedef Application App;
+
enum : unsigned {
MaximumSize = ~0u,
MinimumSize = 0u,
@@ -76,9 +112,11 @@ struct Geometry {
enum class Orientation : unsigned { Horizontal, Vertical };
struct Font {
- nall::string description;
- Geometry geometry(const nall::string &text);
- Font(const nall::string &description = "");
+ static nall::string serif(unsigned size = 0, const nall::string &style = "");
+ static nall::string sans(unsigned size = 0, const nall::string &style = "");
+ static nall::string monospace(unsigned size = 0, const nall::string &style = "");
+ static Size size(const nall::string &font, const nall::string &text);
+ Font() = delete;
};
struct Desktop {
@@ -119,6 +157,7 @@ struct MessageWindow {
Ok,
OkCancel,
YesNo,
+ YesNoCancel,
};
enum class Response : unsigned {
@@ -143,19 +182,8 @@ struct Object {
pObject &p;
};
-struct OS {
- static void main();
- static bool pendingEvents();
- static void processEvents();
- static void quit();
- static void setName(const nall::string &name);
-
- struct State;
- static void initialize();
-};
-
struct Timer : private nall::base_from_member, Object {
- nall::function onTimeout;
+ nall::function onActivate;
void setEnabled(bool enabled = true);
void setInterval(unsigned milliseconds);
@@ -190,7 +218,6 @@ struct Window : private nall::base_from_member, Object {
bool focused();
bool fullScreen();
Geometry geometry();
- void ignore();
void remove_(Layout &layout);
void remove_(Menu &menu);
void remove_(Widget &widget);
@@ -304,7 +331,7 @@ struct RadioItem : private nall::base_from_member, Action {
struct Sizable : Object {
virtual bool enabled() = 0;
Layout* layout();
- virtual Geometry minimumGeometry() = 0;
+ virtual Size minimumSize() = 0;
virtual void setEnabled(bool enabled = true) = 0;
virtual void setGeometry(const Geometry &geometry) = 0;
virtual void setVisible(bool visible = true) = 0;
@@ -337,7 +364,7 @@ struct Widget : private nall::base_from_member, Sizable {
bool focused();
nall::string font();
Geometry geometry();
- Geometry minimumGeometry();
+ Size minimumSize();
void setEnabled(bool enabled = true);
void setFocused();
void setFont(const nall::string &font);
@@ -385,21 +412,21 @@ struct Canvas : private nall::base_from_member, Widget {
pCanvas &p;
};
-struct CheckBox : private nall::base_from_member, Widget {
+struct CheckButton : private nall::base_from_member, Widget {
nall::function onToggle;
bool checked();
void setChecked(bool checked = true);
void setText(const nall::string &text);
- CheckBox();
- ~CheckBox();
+ CheckButton();
+ ~CheckButton();
struct State;
State &state;
- pCheckBox &p;
+ pCheckButton &p;
};
-struct ComboBox : private nall::base_from_member, Widget {
+struct ComboButton : private nall::base_from_member, Widget {
nall::function onChange;
template void append(const Args&... args) { append_({args...}); }
@@ -413,11 +440,11 @@ struct ComboBox : private nall::base_from_member, Widget {
nall::string text();
nall::string text(unsigned row);
- ComboBox();
- ~ComboBox();
+ ComboButton();
+ ~ComboButton();
struct State;
State &state;
- pComboBox &p;
+ pComboButton &p;
};
struct HexEdit : private nall::base_from_member, Widget {
@@ -437,7 +464,7 @@ struct HexEdit : private nall::base_from_member, Widget {
pHexEdit &p;
};
-struct HorizontalScrollBar : private nall::base_from_member, Widget {
+struct HorizontalScroller : private nall::base_from_member, Widget {
nall::function onChange;
unsigned length();
@@ -445,11 +472,11 @@ struct HorizontalScrollBar : private nall::base_from_member, Widget {
@@ -534,9 +561,9 @@ struct ProgressBar : private nall::base_from_member, Widget {
pProgressBar &p;
};
-struct RadioBox : private nall::base_from_member, Widget {
+struct RadioButton : private nall::base_from_member, Widget {
template static void group(Args&... args) { group({args...}); }
- static void group(const nall::set &list);
+ static void group(const nall::set &list);
nall::function onActivate;
@@ -544,11 +571,11 @@ struct RadioBox : private nall::base_from_member, Widget {
void setChecked();
void setText(const nall::string &text);
- RadioBox();
- ~RadioBox();
+ RadioButton();
+ ~RadioButton();
struct State;
State &state;
- pRadioBox &p;
+ pRadioButton &p;
};
struct TextEdit : private nall::base_from_member, Widget {
@@ -559,6 +586,7 @@ struct TextEdit : private nall::base_from_member, Widget {
void setText(const nall::string &text);
void setWordWrap(bool wordWrap = true);
nall::string text();
+ bool wordWrap();
TextEdit();
~TextEdit();
@@ -567,7 +595,7 @@ struct TextEdit : private nall::base_from_member, Widget {
pTextEdit &p;
};
-struct VerticalScrollBar : private nall::base_from_member, Widget {
+struct VerticalScroller : private nall::base_from_member, Widget {
nall::function onChange;
unsigned length();
@@ -575,11 +603,11 @@ struct VerticalScrollBar : private nall::base_from_member,
void setLength(unsigned length);
void setPosition(unsigned position);
- VerticalScrollBar();
- ~VerticalScrollBar();
+ VerticalScroller();
+ ~VerticalScroller();
struct State;
State &state;
- pVerticalScrollBar &p;
+ pVerticalScroller &p;
};
struct VerticalSlider : private nall::base_from_member, Widget {
@@ -613,3 +641,5 @@ struct Viewport : private nall::base_from_member, Widget {
#include "layout/fixed-layout.hpp"
#include "layout/horizontal-layout.hpp"
#include "layout/vertical-layout.hpp"
+
+}
diff --git a/higan/phoenix/core/layout/fixed-layout.cpp b/higan/phoenix/core/layout/fixed-layout.cpp
index 71ff3dac..7665123b 100755
--- a/higan/phoenix/core/layout/fixed-layout.cpp
+++ b/higan/phoenix/core/layout/fixed-layout.cpp
@@ -15,13 +15,13 @@ bool FixedLayout::enabled() {
return state.enabled;
}
-Geometry FixedLayout::minimumGeometry() {
+Size FixedLayout::minimumSize() {
unsigned width = MinimumSize, height = MinimumSize;
for(auto &child : children) {
- width = max(width, child.sizable->minimumGeometry().width);
- height = max(height, child.sizable->minimumGeometry().height);
+ width = max(width, child.sizable->minimumSize().width);
+ height = max(height, child.sizable->minimumSize().height);
}
- return { 0, 0, width, height };
+ return {width, height};
}
void FixedLayout::remove(Sizable &sizable) {
diff --git a/higan/phoenix/core/layout/fixed-layout.hpp b/higan/phoenix/core/layout/fixed-layout.hpp
index a67f2185..a8b3bab0 100755
--- a/higan/phoenix/core/layout/fixed-layout.hpp
+++ b/higan/phoenix/core/layout/fixed-layout.hpp
@@ -2,7 +2,7 @@ struct FixedLayout : Layout {
void append(Sizable &sizable, const Geometry &geometry);
void append(Sizable &sizable);
bool enabled();
- Geometry minimumGeometry();
+ Size minimumSize();
void remove(Sizable &sizable);
void reset();
void setEnabled(bool enabled = true);
diff --git a/higan/phoenix/core/layout/horizontal-layout.cpp b/higan/phoenix/core/layout/horizontal-layout.cpp
index a1146038..67fdeaf2 100755
--- a/higan/phoenix/core/layout/horizontal-layout.cpp
+++ b/higan/phoenix/core/layout/horizontal-layout.cpp
@@ -1,6 +1,6 @@
void HorizontalLayout::append(Sizable &sizable, const Size &size, unsigned spacing) {
for(auto &child : children) if(child.sizable == &sizable) return;
- children.append({ &sizable, size.width, size.height, spacing });
+ children.append({&sizable, size.width, size.height, spacing});
synchronizeLayout();
if(window()) window()->synchronizeLayout();
}
@@ -16,13 +16,13 @@ bool HorizontalLayout::enabled() {
return state.enabled;
}
-Geometry HorizontalLayout::minimumGeometry() {
+Size HorizontalLayout::minimumSize() {
unsigned width = 0, height = 0;
for(auto &child : children) {
width += child.spacing;
if(child.width == MinimumSize || child.width == MaximumSize) {
- width += child.sizable->minimumGeometry().width;
+ width += child.sizable->minimumSize().width;
continue;
}
width += child.width;
@@ -30,13 +30,13 @@ Geometry HorizontalLayout::minimumGeometry() {
for(auto &child : children) {
if(child.height == MinimumSize || child.height == MaximumSize) {
- height = max(height, child.sizable->minimumGeometry().height);
+ height = max(height, child.sizable->minimumSize().height);
continue;
}
height = max(height, child.height);
}
- return { 0, 0, state.margin * 2 + width, state.margin * 2 + height };
+ return {state.margin * 2 + width, state.margin * 2 + height};
}
void HorizontalLayout::remove(Sizable &sizable) {
@@ -75,8 +75,8 @@ void HorizontalLayout::setEnabled(bool enabled) {
void HorizontalLayout::setGeometry(const Geometry &containerGeometry) {
auto children = this->children;
for(auto &child : children) {
- if(child.width == MinimumSize) child.width = child.sizable->minimumGeometry().width;
- if(child.height == MinimumSize) child.height = child.sizable->minimumGeometry().height;
+ if(child.width == MinimumSize) child.width = child.sizable->minimumSize().width;
+ if(child.height == MinimumSize) child.height = child.sizable->minimumSize().height;
}
Geometry geometry = containerGeometry;
@@ -102,7 +102,7 @@ void HorizontalLayout::setGeometry(const Geometry &containerGeometry) {
for(auto &child : children) {
unsigned pivot = (maximumHeight - child.height) * state.alignment;
- Geometry childGeometry = { geometry.x, geometry.y + pivot, child.width, child.height };
+ Geometry childGeometry = {geometry.x, geometry.y + pivot, child.width, child.height};
child.sizable->setGeometry(childGeometry);
geometry.x += child.width + child.spacing;
diff --git a/higan/phoenix/core/layout/horizontal-layout.hpp b/higan/phoenix/core/layout/horizontal-layout.hpp
index 96d4f101..8a25f52f 100755
--- a/higan/phoenix/core/layout/horizontal-layout.hpp
+++ b/higan/phoenix/core/layout/horizontal-layout.hpp
@@ -2,7 +2,7 @@ struct HorizontalLayout : public Layout {
void append(Sizable &sizable, const Size &size, unsigned spacing = 0);
void append(Sizable &sizable);
bool enabled();
- Geometry minimumGeometry();
+ Size minimumSize();
void remove(Sizable &sizable);
void reset();
void setAlignment(double alignment);
diff --git a/higan/phoenix/core/layout/vertical-layout.cpp b/higan/phoenix/core/layout/vertical-layout.cpp
index 4fd6315b..f1940965 100755
--- a/higan/phoenix/core/layout/vertical-layout.cpp
+++ b/higan/phoenix/core/layout/vertical-layout.cpp
@@ -1,6 +1,6 @@
void VerticalLayout::append(Sizable &sizable, const Size &size, unsigned spacing) {
for(auto &child : children) if(child.sizable == &sizable) return;
- children.append({ &sizable, size.width, size.height, spacing });
+ children.append({&sizable, size.width, size.height, spacing});
synchronizeLayout();
if(window()) window()->synchronizeLayout();
}
@@ -16,12 +16,12 @@ bool VerticalLayout::enabled() {
return state.enabled;
}
-Geometry VerticalLayout::minimumGeometry() {
+Size VerticalLayout::minimumSize() {
unsigned width = 0, height = 0;
for(auto &child : children) {
if(child.width == MinimumSize || child.width == MaximumSize) {
- width = max(width, child.sizable->minimumGeometry().width);
+ width = max(width, child.sizable->minimumSize().width);
continue;
}
width = max(width, child.width);
@@ -30,13 +30,13 @@ Geometry VerticalLayout::minimumGeometry() {
for(auto &child : children) {
height += child.spacing;
if(child.height == MinimumSize || child.height == MaximumSize) {
- height += child.sizable->minimumGeometry().height;
+ height += child.sizable->minimumSize().height;
continue;
}
height += child.height;
}
- return { 0, 0, state.margin * 2 + width, state.margin * 2 + height };
+ return {state.margin * 2 + width, state.margin * 2 + height};
}
void VerticalLayout::remove(Sizable &sizable) {
@@ -75,8 +75,8 @@ void VerticalLayout::setEnabled(bool enabled) {
void VerticalLayout::setGeometry(const Geometry &containerGeometry) {
auto children = this->children;
for(auto &child : children) {
- if(child.width == MinimumSize) child.width = child.sizable->minimumGeometry().width;
- if(child.height == MinimumSize) child.height = child.sizable->minimumGeometry().height;
+ if(child.width == MinimumSize) child.width = child.sizable->minimumSize().width;
+ if(child.height == MinimumSize) child.height = child.sizable->minimumSize().height;
}
Geometry geometry = containerGeometry;
@@ -102,7 +102,7 @@ void VerticalLayout::setGeometry(const Geometry &containerGeometry) {
for(auto &child : children) {
unsigned pivot = (maximumWidth - child.width) * state.alignment;
- Geometry childGeometry = { geometry.x + pivot, geometry.y, child.width, child.height };
+ Geometry childGeometry = {geometry.x + pivot, geometry.y, child.width, child.height};
child.sizable->setGeometry(childGeometry);
geometry.y += child.height + child.spacing;
diff --git a/higan/phoenix/core/layout/vertical-layout.hpp b/higan/phoenix/core/layout/vertical-layout.hpp
index 8273dbe2..887bb24c 100755
--- a/higan/phoenix/core/layout/vertical-layout.hpp
+++ b/higan/phoenix/core/layout/vertical-layout.hpp
@@ -2,7 +2,7 @@ struct VerticalLayout : public Layout {
void append(Sizable &sizable, const Size &size, unsigned spacing = 0);
void append(Sizable &sizable);
bool enabled();
- Geometry minimumGeometry();
+ Size minimumSize();
void remove(Sizable &sizable);
void reset();
void setAlignment(double alignment);
diff --git a/higan/phoenix/core/state.hpp b/higan/phoenix/core/state.hpp
index a4bff751..bbc259b4 100755
--- a/higan/phoenix/core/state.hpp
+++ b/higan/phoenix/core/state.hpp
@@ -1,205 +1,115 @@
-struct OS::State {
+struct Application::State {
string name;
-
- State() {
- }
-} osState;
+ bool quit = false;
+} applicationState;
struct Timer::State {
- bool enabled;
- unsigned milliseconds;
-
- State() {
- enabled = false;
- milliseconds = 0;
- }
+ bool enabled = false;
+ unsigned milliseconds = 0;
};
struct Window::State {
- bool backgroundColorOverride;
- Color backgroundColor;
- bool fullScreen;
- Geometry geometry;
- bool ignore;
+ bool backgroundColorOverride = false;
+ Color backgroundColor = {0, 0, 0, 255};
+ bool fullScreen = false;
+ Geometry geometry = {128, 128, 256, 256};
set layout;
set