diff options
Diffstat (limited to 'src/libui_sdl')
| -rw-r--r-- | src/libui_sdl/DlgEmuSettings.cpp | 252 | ||||
| -rw-r--r-- | src/libui_sdl/libui/ui.h | 764 | ||||
| -rw-r--r-- | src/libui_sdl/libui/unix/stddialogs.c | 126 | ||||
| -rw-r--r-- | src/libui_sdl/libui/windows/stddialogs.cpp | 180 | ||||
| -rw-r--r-- | src/libui_sdl/main.cpp | 3061 | 
5 files changed, 4383 insertions, 0 deletions
| diff --git a/src/libui_sdl/DlgEmuSettings.cpp b/src/libui_sdl/DlgEmuSettings.cpp new file mode 100644 index 0000000..0df9c6c --- /dev/null +++ b/src/libui_sdl/DlgEmuSettings.cpp @@ -0,0 +1,252 @@ +/* +    Copyright 2016-2020 Arisotura + +    This file is part of melonDS. + +    melonDS is free software: you can redistribute it and/or modify it under +    the terms of the GNU General Public License as published by the Free +    Software Foundation, either version 3 of the License, or (at your option) +    any later version. + +    melonDS is distributed in the hope that it will be useful, but WITHOUT ANY +    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +    FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License along +    with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdlib.h> +#include <stdio.h> + +#include "libui/ui.h" + +#include "../types.h" +#include "PlatformConfig.h" + +#include "DlgEmuSettings.h" + + +void ApplyNewSettings(int type); + +extern bool RunningSomething; + +namespace DlgEmuSettings +{ + +bool opened; +uiWindow* win; + +uiCheckbox* cbDirectBoot; + +#ifdef JIT_ENABLED +uiCheckbox* cbJITEnabled; +uiEntry* enJITMaxBlockSize; +uiCheckbox* cbJITBranchOptimisations; +uiCheckbox* cbJITLiteralOptimisations; +#endif + +int OnCloseWindow(uiWindow* window, void* blarg) +{ +    opened = false; +    return 1; +} + +void OnCancel(uiButton* btn, void* blarg) +{ +    uiControlDestroy(uiControl(win)); +    opened = false; +} + +void OnOk(uiButton* btn, void* blarg) +{ +#ifdef JIT_ENABLED +    bool restart = false; + +    bool enableJit = uiCheckboxChecked(cbJITEnabled); +    char* maxBlockSizeStr = uiEntryText(enJITMaxBlockSize); +    long blockSize = strtol(maxBlockSizeStr, NULL, 10); +    bool branchOptimisations = uiCheckboxChecked(cbJITBranchOptimisations); +    bool literalOptimisations = uiCheckboxChecked(cbJITLiteralOptimisations); +    uiFreeText(maxBlockSizeStr); +    if (blockSize < 1) +        blockSize = 1; +    if (blockSize > 32) +        blockSize = 32; + +    if (enableJit != Config::JIT_Enable || blockSize != Config::JIT_MaxBlockSize +        || branchOptimisations != Config::JIT_BrancheOptimisations +        || literalOptimisations != Config::JIT_LiteralOptimisations) +    { +        if (RunningSomething &&  +            !uiMsgBoxConfirm(win, "Reset emulator",  +                "Changing JIT settings requires a reset.\n\nDo you want to continue?")) +            return; + +        Config::JIT_Enable = enableJit; +        Config::JIT_MaxBlockSize = blockSize; +        Config::JIT_BrancheOptimisations = branchOptimisations; +        Config::JIT_LiteralOptimisations = literalOptimisations; + +        restart = true; +    } +#endif + +    Config::DirectBoot = uiCheckboxChecked(cbDirectBoot); + +    Config::Save(); + +    uiControlDestroy(uiControl(win)); +    opened = false; + +#ifdef JIT_ENABLED +    if (restart) +        ApplyNewSettings(4); +#endif +} + +#ifdef JIT_ENABLED +void OnJITStateChanged(uiCheckbox* cb, void* blarg) +{ +    if (uiCheckboxChecked(cb)) +    { +        uiControlEnable(uiControl(enJITMaxBlockSize)); +        uiControlEnable(uiControl(cbJITBranchOptimisations)); +        uiControlEnable(uiControl(cbJITLiteralOptimisations)); +    } +    else +    { +        uiControlDisable(uiControl(enJITMaxBlockSize)); +        uiControlDisable(uiControl(cbJITBranchOptimisations)); +        uiControlDisable(uiControl(cbJITLiteralOptimisations)); +    } +} +#endif + +void Open() +{ +    if (opened) +    { +        uiControlSetFocus(uiControl(win)); +        return; +    } + +    opened = true; +    win = uiNewWindow("Emu settings - melonDS", 300, 50, 0, 0, 0); +    uiWindowSetMargined(win, 1); +    uiWindowOnClosing(win, OnCloseWindow, NULL); + +    uiBox* top = uiNewVerticalBox(); +    uiWindowSetChild(win, uiControl(top)); + +    { +        uiBox* in_ctrl = uiNewVerticalBox(); +        uiBoxAppend(top, uiControl(in_ctrl), 0); + +        cbDirectBoot = uiNewCheckbox("Boot game directly"); +        uiBoxAppend(in_ctrl, uiControl(cbDirectBoot), 0); +    } + +#ifdef JIT_ENABLED +    { +        uiLabel* dummy = uiNewLabel(""); +        uiBoxAppend(top, uiControl(dummy), 0); +    } + +    { +        uiGroup* grp = uiNewGroup("JIT"); +        uiBoxAppend(top, uiControl(grp), 1); + +        uiBox* in_ctrl = uiNewVerticalBox(); +        uiGroupSetChild(grp, uiControl(in_ctrl)); + +        cbJITEnabled = uiNewCheckbox("Enable JIT recompiler"); +        uiBoxAppend(in_ctrl, uiControl(cbJITEnabled), 0); + +        uiCheckboxOnToggled(cbJITEnabled, OnJITStateChanged, NULL); + +        { +            uiBox* row = uiNewHorizontalBox(); +            uiBoxAppend(in_ctrl, uiControl(row), 0); + +            uiLabel* lbl = uiNewLabel("Maximum block size (1-32): "); +            uiBoxAppend(row, uiControl(lbl), 0); + +            enJITMaxBlockSize = uiNewEntry(); +            uiBoxAppend(row, uiControl(enJITMaxBlockSize), 0); +        } + +        { +            uiBox* row = uiNewHorizontalBox(); +            uiBoxAppend(in_ctrl, uiControl(row), 0); + +            uiLabel* lbl = uiNewLabel("If you experience problems with a certain game, you can try disabling these options:"); +            uiBoxAppend(row, uiControl(lbl), 0); +        } + +        { +            uiBox* row = uiNewHorizontalBox(); +            uiBoxAppend(in_ctrl, uiControl(row), 0); + +            cbJITBranchOptimisations = uiNewCheckbox("Branch optimisations"); +            uiBoxAppend(row, uiControl(cbJITBranchOptimisations), 0); +        } + +        { +            uiBox* row = uiNewHorizontalBox(); +            uiBoxAppend(in_ctrl, uiControl(row), 0); + +            cbJITLiteralOptimisations = uiNewCheckbox("Literal optimisations"); +            uiBoxAppend(row, uiControl(cbJITLiteralOptimisations), 0); +        } +    } +#endif + +    { +        uiLabel* dummy = uiNewLabel(""); +        uiBoxAppend(top, uiControl(dummy), 0); +    } + +    { +        uiBox* in_ctrl = uiNewHorizontalBox(); +        uiBoxSetPadded(in_ctrl, 1); +        uiBoxAppend(top, uiControl(in_ctrl), 0); + +        uiLabel* dummy = uiNewLabel(""); +        uiBoxAppend(in_ctrl, uiControl(dummy), 1); + +        uiButton* btncancel = uiNewButton("Cancel"); +        uiButtonOnClicked(btncancel, OnCancel, NULL); +        uiBoxAppend(in_ctrl, uiControl(btncancel), 0); + +        uiButton* btnok = uiNewButton("Ok"); +        uiButtonOnClicked(btnok, OnOk, NULL); +        uiBoxAppend(in_ctrl, uiControl(btnok), 0); +    } + +    uiCheckboxSetChecked(cbDirectBoot, Config::DirectBoot); + +#ifdef JIT_ENABLED +    uiCheckboxSetChecked(cbJITEnabled, Config::JIT_Enable); +    { +        char maxBlockSizeStr[10]; +        sprintf(maxBlockSizeStr, "%d", Config::JIT_MaxBlockSize); +        uiEntrySetText(enJITMaxBlockSize, maxBlockSizeStr); +    } +    OnJITStateChanged(cbJITEnabled, NULL); + +    uiCheckboxSetChecked(cbJITBranchOptimisations, Config::JIT_BrancheOptimisations); +    uiCheckboxSetChecked(cbJITLiteralOptimisations, Config::JIT_LiteralOptimisations); +#endif + +    uiControlShow(uiControl(win)); +} + +void Close() +{ +    if (!opened) return; +    uiControlDestroy(uiControl(win)); +    opened = false; +} + +} diff --git a/src/libui_sdl/libui/ui.h b/src/libui_sdl/libui/ui.h new file mode 100644 index 0000000..e45fe91 --- /dev/null +++ b/src/libui_sdl/libui/ui.h @@ -0,0 +1,764 @@ +// 6 april 2015 + +// TODO add a uiVerifyControlType() function that can be used by control implementations to verify controls + +#ifndef __LIBUI_UI_H__ +#define __LIBUI_UI_H__ + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// this macro is generated by cmake +#ifdef libui_EXPORTS +#ifdef _WIN32 +#define _UI_EXTERN __declspec(dllexport) extern +#else +#define _UI_EXTERN __attribute__((visibility("default"))) extern +#endif +#else +// TODO add __declspec(dllimport) on windows, but only if not static +#define _UI_EXTERN extern +#endif + +// C++ is really really really really really really dumb about enums, so screw that and just make them anonymous +// This has the advantage of being ABI-able should we ever need an ABI... +#define _UI_ENUM(s) typedef unsigned int s; enum + +// This constant is provided because M_PI is nonstandard. +// This comes from Go's math.Pi, which in turn comes from http://oeis.org/A000796. +#define uiPi 3.14159265358979323846264338327950288419716939937510582097494459 + +// TODO uiBool? + +typedef struct uiInitOptions uiInitOptions; + +struct uiInitOptions { +	size_t Size; +}; + +_UI_EXTERN const char *uiInit(uiInitOptions *options); +_UI_EXTERN void uiUninit(void); +_UI_EXTERN void uiFreeInitError(const char *err); + +_UI_EXTERN void uiMain(void); +_UI_EXTERN void uiMainSteps(void); +_UI_EXTERN int uiMainStep(int wait); +_UI_EXTERN void uiQuit(void); + +_UI_EXTERN void uiQueueMain(void (*f)(void *data), void *data); + +_UI_EXTERN void uiOnShouldQuit(int (*f)(void *data), void *data); + +_UI_EXTERN void uiFreeText(char *text); + +typedef struct uiControl uiControl; + +struct uiControl { +	uint32_t Signature; +	uint32_t OSSignature; +	uint32_t TypeSignature; +	void (*Destroy)(uiControl *); +	uintptr_t (*Handle)(uiControl *); +	uiControl *(*Parent)(uiControl *); +	void (*SetParent)(uiControl *, uiControl *); +	int (*Toplevel)(uiControl *); +	int (*Visible)(uiControl *); +	void (*Show)(uiControl *); +	void (*Hide)(uiControl *); +	int (*Enabled)(uiControl *); +	void (*Enable)(uiControl *); +	void (*Disable)(uiControl *); +	void (*SetFocus)(uiControl *); +	void (*SetMinSize)(uiControl*, int, int); + +	int MinWidth, MinHeight; + +	void* UserData; +}; +// TOOD add argument names to all arguments +#define uiControl(this) ((uiControl *) (this)) +_UI_EXTERN void uiControlDestroy(uiControl *); +_UI_EXTERN uintptr_t uiControlHandle(uiControl *); +_UI_EXTERN uiControl *uiControlParent(uiControl *); +_UI_EXTERN void uiControlSetParent(uiControl *, uiControl *); +_UI_EXTERN int uiControlToplevel(uiControl *); +_UI_EXTERN int uiControlVisible(uiControl *); +_UI_EXTERN void uiControlShow(uiControl *); +_UI_EXTERN void uiControlHide(uiControl *); +_UI_EXTERN int uiControlEnabled(uiControl *); +_UI_EXTERN void uiControlEnable(uiControl *); +_UI_EXTERN void uiControlDisable(uiControl *); +_UI_EXTERN void uiControlSetFocus(uiControl *); +_UI_EXTERN void uiControlSetMinSize(uiControl *, int w, int h); // -1 = no minimum + +_UI_EXTERN uiControl *uiAllocControl(size_t n, uint32_t OSsig, uint32_t typesig, const char *typenamestr); +_UI_EXTERN void uiFreeControl(uiControl *); + +// TODO make sure all controls have these +_UI_EXTERN void uiControlVerifySetParent(uiControl *, uiControl *); +_UI_EXTERN int uiControlEnabledToUser(uiControl *); + +_UI_EXTERN void uiUserBugCannotSetParentOnToplevel(const char *type); + +typedef struct uiWindow uiWindow; +#define uiWindow(this) ((uiWindow *) (this)) +_UI_EXTERN char *uiWindowTitle(uiWindow *w); +_UI_EXTERN void uiWindowSetTitle(uiWindow *w, const char *title); +_UI_EXTERN void uiWindowPosition(uiWindow *w, int *x, int *y); +_UI_EXTERN void uiWindowSetPosition(uiWindow *w, int x, int y); +_UI_EXTERN void uiWindowContentSize(uiWindow *w, int *width, int *height); +_UI_EXTERN void uiWindowSetContentSize(uiWindow *w, int width, int height); +_UI_EXTERN int uiWindowMinimized(uiWindow *w); +_UI_EXTERN void uiWindowSetMinimized(uiWindow *w, int minimized); +_UI_EXTERN int uiWindowMaximized(uiWindow *w); +_UI_EXTERN void uiWindowSetMaximized(uiWindow *w, int maximized); +_UI_EXTERN int uiWindowFullscreen(uiWindow *w); +_UI_EXTERN void uiWindowSetFullscreen(uiWindow *w, int fullscreen); +_UI_EXTERN int uiWindowBorderless(uiWindow *w); +_UI_EXTERN void uiWindowSetBorderless(uiWindow *w, int borderless); +_UI_EXTERN void uiWindowSetChild(uiWindow *w, uiControl *child); +_UI_EXTERN int uiWindowMargined(uiWindow *w); +_UI_EXTERN void uiWindowSetMargined(uiWindow *w, int margined); +_UI_EXTERN void uiWindowSetDropTarget(uiWindow* w, int drop); +_UI_EXTERN uiWindow *uiNewWindow(const char *title, int width, int height, int maximized, int hasMenubar, int resizable); + +_UI_EXTERN void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data); +_UI_EXTERN void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *w, void *data), void *data); +_UI_EXTERN void uiWindowOnDropFile(uiWindow *w, void (*f)(uiWindow *w, char *file, void *data), void *data); +_UI_EXTERN void uiWindowOnGetFocus(uiWindow *w, void (*f)(uiWindow *w, void *data), void *data); +_UI_EXTERN void uiWindowOnLoseFocus(uiWindow *w, void (*f)(uiWindow *w, void *data), void *data); + +typedef struct uiButton uiButton; +#define uiButton(this) ((uiButton *) (this)) +_UI_EXTERN char *uiButtonText(uiButton *b); +_UI_EXTERN void uiButtonSetText(uiButton *b, const char *text); +_UI_EXTERN void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *b, void *data), void *data); +_UI_EXTERN uiButton *uiNewButton(const char *text); + +typedef struct uiBox uiBox; +#define uiBox(this) ((uiBox *) (this)) +_UI_EXTERN void uiBoxAppend(uiBox *b, uiControl *child, int stretchy); +_UI_EXTERN void uiBoxDelete(uiBox *b, int index); +_UI_EXTERN int uiBoxPadded(uiBox *b); +_UI_EXTERN void uiBoxSetPadded(uiBox *b, int padded); +_UI_EXTERN uiBox *uiNewHorizontalBox(void); +_UI_EXTERN uiBox *uiNewVerticalBox(void); + +typedef struct uiCheckbox uiCheckbox; +#define uiCheckbox(this) ((uiCheckbox *) (this)) +_UI_EXTERN char *uiCheckboxText(uiCheckbox *c); +_UI_EXTERN void uiCheckboxSetText(uiCheckbox *c, const char *text); +_UI_EXTERN void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *c, void *data), void *data); +_UI_EXTERN int uiCheckboxChecked(uiCheckbox *c); +_UI_EXTERN void uiCheckboxSetChecked(uiCheckbox *c, int checked); +_UI_EXTERN uiCheckbox *uiNewCheckbox(const char *text); + +typedef struct uiEntry uiEntry; +#define uiEntry(this) ((uiEntry *) (this)) +_UI_EXTERN char *uiEntryText(uiEntry *e); +_UI_EXTERN void uiEntrySetText(uiEntry *e, const char *text); +_UI_EXTERN void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *e, void *data), void *data); +_UI_EXTERN int uiEntryReadOnly(uiEntry *e); +_UI_EXTERN void uiEntrySetReadOnly(uiEntry *e, int readonly); +_UI_EXTERN uiEntry *uiNewEntry(void); +_UI_EXTERN uiEntry *uiNewPasswordEntry(void); +_UI_EXTERN uiEntry *uiNewSearchEntry(void); + +typedef struct uiLabel uiLabel; +#define uiLabel(this) ((uiLabel *) (this)) +_UI_EXTERN char *uiLabelText(uiLabel *l); +_UI_EXTERN void uiLabelSetText(uiLabel *l, const char *text); +_UI_EXTERN uiLabel *uiNewLabel(const char *text); + +typedef struct uiTab uiTab; +#define uiTab(this) ((uiTab *) (this)) +_UI_EXTERN void uiTabAppend(uiTab *t, const char *name, uiControl *c); +_UI_EXTERN void uiTabInsertAt(uiTab *t, const char *name, int before, uiControl *c); +_UI_EXTERN void uiTabDelete(uiTab *t, int index); +_UI_EXTERN int uiTabNumPages(uiTab *t); +_UI_EXTERN int uiTabMargined(uiTab *t, int page); +_UI_EXTERN void uiTabSetMargined(uiTab *t, int page, int margined); +_UI_EXTERN uiTab *uiNewTab(void); + +typedef struct uiGroup uiGroup; +#define uiGroup(this) ((uiGroup *) (this)) +_UI_EXTERN char *uiGroupTitle(uiGroup *g); +_UI_EXTERN void uiGroupSetTitle(uiGroup *g, const char *title); +_UI_EXTERN void uiGroupSetChild(uiGroup *g, uiControl *c); +_UI_EXTERN int uiGroupMargined(uiGroup *g); +_UI_EXTERN void uiGroupSetMargined(uiGroup *g, int margined); +_UI_EXTERN uiGroup *uiNewGroup(const char *title); + +// spinbox/slider rules: +// setting value outside of range will automatically clamp +// initial value is minimum +// complaint if min >= max? + +typedef struct uiSpinbox uiSpinbox; +#define uiSpinbox(this) ((uiSpinbox *) (this)) +_UI_EXTERN int uiSpinboxValue(uiSpinbox *s); +_UI_EXTERN void uiSpinboxSetValue(uiSpinbox *s, int value); +_UI_EXTERN void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *s, void *data), void *data); +_UI_EXTERN uiSpinbox *uiNewSpinbox(int min, int max); + +typedef struct uiSlider uiSlider; +#define uiSlider(this) ((uiSlider *) (this)) +_UI_EXTERN int uiSliderValue(uiSlider *s); +_UI_EXTERN void uiSliderSetValue(uiSlider *s, int value); +_UI_EXTERN void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *s, void *data), void *data); +_UI_EXTERN uiSlider *uiNewSlider(int min, int max); + +typedef struct uiProgressBar uiProgressBar; +#define uiProgressBar(this) ((uiProgressBar *) (this)) +_UI_EXTERN int uiProgressBarValue(uiProgressBar *p); +_UI_EXTERN void uiProgressBarSetValue(uiProgressBar *p, int n); +_UI_EXTERN uiProgressBar *uiNewProgressBar(void); + +typedef struct uiSeparator uiSeparator; +#define uiSeparator(this) ((uiSeparator *) (this)) +_UI_EXTERN uiSeparator *uiNewHorizontalSeparator(void); +_UI_EXTERN uiSeparator *uiNewVerticalSeparator(void); + +typedef struct uiCombobox uiCombobox; +#define uiCombobox(this) ((uiCombobox *) (this)) +_UI_EXTERN void uiComboboxAppend(uiCombobox *c, const char *text); +_UI_EXTERN int uiComboboxSelected(uiCombobox *c); +_UI_EXTERN void uiComboboxSetSelected(uiCombobox *c, int n); +_UI_EXTERN void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data); +_UI_EXTERN uiCombobox *uiNewCombobox(void); + +typedef struct uiEditableCombobox uiEditableCombobox; +#define uiEditableCombobox(this) ((uiEditableCombobox *) (this)) +_UI_EXTERN void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text); +_UI_EXTERN char *uiEditableComboboxText(uiEditableCombobox *c); +_UI_EXTERN void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text); +// TODO what do we call a function that sets the currently selected item and fills the text field with it? editable comboboxes have no consistent concept of selected item +_UI_EXTERN void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data); +_UI_EXTERN uiEditableCombobox *uiNewEditableCombobox(void); + +typedef struct uiRadioButtons uiRadioButtons; +#define uiRadioButtons(this) ((uiRadioButtons *) (this)) +_UI_EXTERN void uiRadioButtonsAppend(uiRadioButtons *r, const char *text); +_UI_EXTERN int uiRadioButtonsSelected(uiRadioButtons *r); +_UI_EXTERN void uiRadioButtonsSetSelected(uiRadioButtons *r, int n); +_UI_EXTERN void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data); +_UI_EXTERN uiRadioButtons *uiNewRadioButtons(void); + +typedef struct uiDateTimePicker uiDateTimePicker; +#define uiDateTimePicker(this) ((uiDateTimePicker *) (this)) +_UI_EXTERN uiDateTimePicker *uiNewDateTimePicker(void); +_UI_EXTERN uiDateTimePicker *uiNewDatePicker(void); +_UI_EXTERN uiDateTimePicker *uiNewTimePicker(void); + +// TODO provide a facility for entering tab stops? +typedef struct uiMultilineEntry uiMultilineEntry; +#define uiMultilineEntry(this) ((uiMultilineEntry *) (this)) +_UI_EXTERN char *uiMultilineEntryText(uiMultilineEntry *e); +_UI_EXTERN void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text); +_UI_EXTERN void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text); +_UI_EXTERN void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data); +_UI_EXTERN int uiMultilineEntryReadOnly(uiMultilineEntry *e); +_UI_EXTERN void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly); +_UI_EXTERN uiMultilineEntry *uiNewMultilineEntry(void); +_UI_EXTERN uiMultilineEntry *uiNewNonWrappingMultilineEntry(void); + +typedef struct uiMenuItem uiMenuItem; +#define uiMenuItem(this) ((uiMenuItem *) (this)) +_UI_EXTERN void uiMenuItemEnable(uiMenuItem *m); +_UI_EXTERN void uiMenuItemDisable(uiMenuItem *m); +_UI_EXTERN void uiMenuItemOnClicked(uiMenuItem *m, void (*f)(uiMenuItem *sender, uiWindow *window, void *data), void *data); +_UI_EXTERN int uiMenuItemChecked(uiMenuItem *m); +_UI_EXTERN void uiMenuItemSetChecked(uiMenuItem *m, int checked); + +typedef struct uiMenu uiMenu; +#define uiMenu(this) ((uiMenu *) (this)) +_UI_EXTERN uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name); +_UI_EXTERN uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name); +_UI_EXTERN uiMenuItem *uiMenuAppendQuitItem(uiMenu *m); +_UI_EXTERN uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m); +_UI_EXTERN uiMenuItem *uiMenuAppendAboutItem(uiMenu *m); +_UI_EXTERN uiMenuItem *uiMenuAppendSubmenu(uiMenu *m, uiMenu* child); +_UI_EXTERN void uiMenuAppendSeparator(uiMenu *m); +_UI_EXTERN uiMenu *uiNewMenu(const char *name); + +_UI_EXTERN char *uiOpenFile(uiWindow *parent, const char* filter, const char* initpath); +_UI_EXTERN char *uiSaveFile(uiWindow *parent, const char* filter, const char* initpath); +_UI_EXTERN void uiMsgBox(uiWindow *parent, const char *title, const char *description); +_UI_EXTERN void uiMsgBoxError(uiWindow *parent, const char *title, const char *description); +_UI_EXTERN int uiMsgBoxConfirm(uiWindow * parent, const char *title, const char *description); + +typedef struct uiArea uiArea; +typedef struct uiAreaHandler uiAreaHandler; +typedef struct uiAreaDrawParams uiAreaDrawParams; +typedef struct uiAreaMouseEvent uiAreaMouseEvent; +typedef struct uiAreaKeyEvent uiAreaKeyEvent; + +typedef struct uiDrawContext uiDrawContext; + +// TO CONSIDER: the uiAreaHandler param there seems useless +// (might use individual callbacks instead of handler struct?) +struct uiAreaHandler { +	void (*Draw)(uiAreaHandler *, uiArea *, uiAreaDrawParams *); +	// TODO document that resizes cause a full redraw for non-scrolling areas; implementation-defined for scrolling areas +	void (*MouseEvent)(uiAreaHandler *, uiArea *, uiAreaMouseEvent *); +	// TODO document that on first show if the mouse is already in the uiArea then one gets sent with left=0 +	// TODO what about when the area is hidden and then shown again? +	void (*MouseCrossed)(uiAreaHandler *, uiArea *, int left); +	void (*DragBroken)(uiAreaHandler *, uiArea *); +	int (*KeyEvent)(uiAreaHandler *, uiArea *, uiAreaKeyEvent *); +	void (*Resize)(uiAreaHandler *, uiArea *, int, int); +}; + +// TODO RTL layouts? +// TODO reconcile edge and corner naming +_UI_ENUM(uiWindowResizeEdge) { +	uiWindowResizeEdgeLeft, +	uiWindowResizeEdgeTop, +	uiWindowResizeEdgeRight, +	uiWindowResizeEdgeBottom, +	uiWindowResizeEdgeTopLeft, +	uiWindowResizeEdgeTopRight, +	uiWindowResizeEdgeBottomLeft, +	uiWindowResizeEdgeBottomRight, +	// TODO have one for keyboard resizes? +	// TODO GDK doesn't seem to have any others, including for keyboards... +	// TODO way to bring up the system menu instead? +}; + +#define uiGLVersion(major, minor)  ((major) | ((minor)<<16)) +#define uiGLVerMajor(ver)          ((ver) & 0xFFFF) +#define uiGLVerMinor(ver)          ((ver) >> 16) + +#define uiArea(this) ((uiArea *) (this)) +// TODO give a better name +// TODO document the types of width and height +_UI_EXTERN void uiAreaSetSize(uiArea *a, int width, int height); +// TODO uiAreaQueueRedraw() +_UI_EXTERN void uiAreaQueueRedrawAll(uiArea *a); +_UI_EXTERN void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height); +// TODO document these can only be called within Mouse() handlers +// TODO should these be allowed on scrolling areas? +// TODO decide which mouse events should be accepted; Down is the only one guaranteed to work right now +// TODO what happens to events after calling this up to and including the next mouse up? +// TODO release capture? +_UI_EXTERN void uiAreaBeginUserWindowMove(uiArea *a); +_UI_EXTERN void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge); +_UI_EXTERN void uiAreaSetBackgroundColor(uiArea *a, int r, int g, int b); +_UI_EXTERN uiArea *uiNewArea(uiAreaHandler *ah); +_UI_EXTERN uiArea *uiNewGLArea(uiAreaHandler *ah, const unsigned int* req_versions); +_UI_EXTERN uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height); + +struct uiAreaDrawParams { +	uiDrawContext *Context; + +	// TODO document that this is only defined for nonscrolling areas +	double AreaWidth; +	double AreaHeight; + +	double ClipX; +	double ClipY; +	double ClipWidth; +	double ClipHeight; +}; + +typedef struct uiDrawPath uiDrawPath; +typedef struct uiDrawBrush uiDrawBrush; +typedef struct uiDrawStrokeParams uiDrawStrokeParams; +typedef struct uiDrawMatrix uiDrawMatrix; + +typedef struct uiDrawBrushGradientStop uiDrawBrushGradientStop; + +typedef struct uiDrawBitmap uiDrawBitmap; + +_UI_ENUM(uiDrawBrushType) { +	uiDrawBrushTypeSolid, +	uiDrawBrushTypeLinearGradient, +	uiDrawBrushTypeRadialGradient, +	uiDrawBrushTypeImage, +}; + +_UI_ENUM(uiDrawLineCap) { +	uiDrawLineCapFlat, +	uiDrawLineCapRound, +	uiDrawLineCapSquare, +}; + +_UI_ENUM(uiDrawLineJoin) { +	uiDrawLineJoinMiter, +	uiDrawLineJoinRound, +	uiDrawLineJoinBevel, +}; + +// this is the default for botoh cairo and Direct2D (in the latter case, from the C++ helper functions) +// Core Graphics doesn't explicitly specify a default, but NSBezierPath allows you to choose one, and this is the initial value +// so we're good to use it too! +#define uiDrawDefaultMiterLimit 10.0 + +_UI_ENUM(uiDrawFillMode) { +	uiDrawFillModeWinding, +	uiDrawFillModeAlternate, +}; + +struct uiDrawMatrix { +	double M11; +	double M12; +	double M21; +	double M22; +	double M31; +	double M32; +}; + +struct uiDrawBrush { +	uiDrawBrushType Type; + +	// solid brushes +	double R; +	double G; +	double B; +	double A; + +	// gradient brushes +	double X0;		// linear: start X, radial: start X +	double Y0;		// linear: start Y, radial: start Y +	double X1;		// linear: end X, radial: outer circle center X +	double Y1;		// linear: end Y, radial: outer circle center Y +	double OuterRadius;		// radial gradients only +	uiDrawBrushGradientStop *Stops; +	size_t NumStops; +	// TODO extend mode +	// cairo: none, repeat, reflect, pad; no individual control +	// Direct2D: repeat, reflect, pad; no individual control +	// Core Graphics: none, pad; before and after individually +	// TODO cairo documentation is inconsistent about pad + +	// TODO images + +	// TODO transforms +}; + +struct uiDrawBrushGradientStop { +	double Pos; +	double R; +	double G; +	double B; +	double A; +}; + +struct uiDrawStrokeParams { +	uiDrawLineCap Cap; +	uiDrawLineJoin Join; +	// TODO what if this is 0? on windows there will be a crash with dashing +	double Thickness; +	double MiterLimit; +	double *Dashes; +	// TOOD what if this is 1 on Direct2D? +	// TODO what if a dash is 0 on Cairo or Quartz? +	size_t NumDashes; +	double DashPhase; +}; + +struct uiRect { +    int X; +    int Y; +    int Width; +    int Height; +}; + +typedef struct uiRect uiRect; + +_UI_EXTERN uiDrawPath *uiDrawNewPath(uiDrawFillMode fillMode); +_UI_EXTERN void uiDrawFreePath(uiDrawPath *p); + +_UI_EXTERN void uiDrawPathNewFigure(uiDrawPath *p, double x, double y); +_UI_EXTERN void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative); +_UI_EXTERN void uiDrawPathLineTo(uiDrawPath *p, double x, double y); +// notes: angles are both relative to 0 and go counterclockwise +// TODO is the initial line segment on cairo and OS X a proper join? +// TODO what if sweep < 0? +_UI_EXTERN void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative); +_UI_EXTERN void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY); +// TODO quadratic bezier +_UI_EXTERN void uiDrawPathCloseFigure(uiDrawPath *p); + +// TODO effect of these when a figure is already started +_UI_EXTERN void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height); + +_UI_EXTERN void uiDrawPathEnd(uiDrawPath *p); + +_UI_EXTERN void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p); +_UI_EXTERN void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b); + +// TODO primitives: +// - rounded rectangles +// - elliptical arcs +// - quadratic bezier curves + +_UI_EXTERN void uiDrawMatrixSetIdentity(uiDrawMatrix *m); +_UI_EXTERN void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y); +_UI_EXTERN void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y); +_UI_EXTERN void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount); +_UI_EXTERN void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount); +_UI_EXTERN void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src); +_UI_EXTERN int uiDrawMatrixInvertible(uiDrawMatrix *m); +_UI_EXTERN int uiDrawMatrixInvert(uiDrawMatrix *m); +_UI_EXTERN void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y); +_UI_EXTERN void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y); + +_UI_EXTERN void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m); + +// TODO add a uiDrawPathStrokeToFill() or something like that +_UI_EXTERN void uiDrawClip(uiDrawContext *c, uiDrawPath *path); + +_UI_EXTERN void uiDrawSave(uiDrawContext *c); +_UI_EXTERN void uiDrawRestore(uiDrawContext *c); + +// bitmap API +_UI_EXTERN uiDrawBitmap* uiDrawNewBitmap(uiDrawContext* c, int width, int height, int alpha); +_UI_EXTERN void uiDrawBitmapUpdate(uiDrawBitmap* bmp, const void* data); +_UI_EXTERN void uiDrawBitmapDraw(uiDrawContext* c, uiDrawBitmap* bmp, uiRect* srcrect, uiRect* dstrect, int filter); +_UI_EXTERN void uiDrawFreeBitmap(uiDrawBitmap* bmp); + +// TODO manage the use of Text, Font, and TextFont, and of the uiDrawText prefix in general + +///// TODO reconsider this +typedef struct uiDrawFontFamilies uiDrawFontFamilies; + +_UI_EXTERN uiDrawFontFamilies *uiDrawListFontFamilies(void); +_UI_EXTERN int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff); +_UI_EXTERN char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n); +_UI_EXTERN void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff); +///// END TODO + +typedef struct uiDrawTextLayout uiDrawTextLayout; +typedef struct uiDrawTextFont uiDrawTextFont; +typedef struct uiDrawTextFontDescriptor uiDrawTextFontDescriptor; +typedef struct uiDrawTextFontMetrics uiDrawTextFontMetrics; + +_UI_ENUM(uiDrawTextWeight) { +	uiDrawTextWeightThin, +	uiDrawTextWeightUltraLight, +	uiDrawTextWeightLight, +	uiDrawTextWeightBook, +	uiDrawTextWeightNormal, +	uiDrawTextWeightMedium, +	uiDrawTextWeightSemiBold, +	uiDrawTextWeightBold, +	uiDrawTextWeightUltraBold, +	uiDrawTextWeightHeavy, +	uiDrawTextWeightUltraHeavy, +}; + +_UI_ENUM(uiDrawTextItalic) { +	uiDrawTextItalicNormal, +	uiDrawTextItalicOblique, +	uiDrawTextItalicItalic, +}; + +_UI_ENUM(uiDrawTextStretch) { +	uiDrawTextStretchUltraCondensed, +	uiDrawTextStretchExtraCondensed, +	uiDrawTextStretchCondensed, +	uiDrawTextStretchSemiCondensed, +	uiDrawTextStretchNormal, +	uiDrawTextStretchSemiExpanded, +	uiDrawTextStretchExpanded, +	uiDrawTextStretchExtraExpanded, +	uiDrawTextStretchUltraExpanded, +}; + +struct uiDrawTextFontDescriptor { +	const char *Family; +	double Size; +	uiDrawTextWeight Weight; +	uiDrawTextItalic Italic; +	uiDrawTextStretch Stretch; +}; + +struct uiDrawTextFontMetrics { +	double Ascent; +	double Descent; +	double Leading; +	// TODO do these two mean the same across all platforms? +	double UnderlinePos; +	double UnderlineThickness; +}; + +_UI_EXTERN uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc); +_UI_EXTERN void uiDrawFreeTextFont(uiDrawTextFont *font); +_UI_EXTERN uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font); +_UI_EXTERN void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc); +// TODO make copy with given attributes methods? +// TODO yuck this name +_UI_EXTERN void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics); + +// TODO initial line spacing? and what about leading? +_UI_EXTERN uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width); +_UI_EXTERN void uiDrawFreeTextLayout(uiDrawTextLayout *layout); +// TODO get width +_UI_EXTERN void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width); +_UI_EXTERN void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height); + +// and the attributes that you can set on a text layout +_UI_EXTERN void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a); + +_UI_EXTERN void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout); + + +// OpenGL support + +typedef struct uiGLContext uiGLContext; + +_UI_EXTERN uiGLContext *uiAreaGetGLContext(uiArea* a); +_UI_EXTERN void uiGLMakeContextCurrent(uiGLContext* ctx); +_UI_EXTERN void uiGLBegin(uiGLContext* ctx); +_UI_EXTERN void uiGLEnd(uiGLContext* ctx); +_UI_EXTERN unsigned int uiGLGetVersion(uiGLContext* ctx); +_UI_EXTERN void *uiGLGetProcAddress(const char* proc); +_UI_EXTERN int uiGLGetFramebuffer(uiGLContext* ctx); +_UI_EXTERN float uiGLGetFramebufferScale(uiGLContext* ctx); +_UI_EXTERN void uiGLSwapBuffers(uiGLContext* ctx); +_UI_EXTERN void uiGLSetVSync(int sync); + + +_UI_ENUM(uiModifiers) { +	uiModifierCtrl = 1 << 0, +	uiModifierAlt = 1 << 1, +	uiModifierShift = 1 << 2, +	uiModifierSuper = 1 << 3, +}; + +// TODO document drag captures +struct uiAreaMouseEvent { +	// TODO document what these mean for scrolling areas +	double X; +	double Y; + +	// TODO see draw above +	double AreaWidth; +	double AreaHeight; + +	int Down; +	int Up; + +	int Count; + +	uiModifiers Modifiers; + +	uint64_t Held1To64; +}; + +_UI_ENUM(uiExtKey) { +	uiExtKeyEscape = 1, +	uiExtKeyInsert,			// equivalent to "Help" on Apple keyboards +	uiExtKeyDelete, +	uiExtKeyHome, +	uiExtKeyEnd, +	uiExtKeyPageUp, +	uiExtKeyPageDown, +	uiExtKeyUp, +	uiExtKeyDown, +	uiExtKeyLeft, +	uiExtKeyRight, +	uiExtKeyF1,			// F1..F12 are guaranteed to be consecutive +	uiExtKeyF2, +	uiExtKeyF3, +	uiExtKeyF4, +	uiExtKeyF5, +	uiExtKeyF6, +	uiExtKeyF7, +	uiExtKeyF8, +	uiExtKeyF9, +	uiExtKeyF10, +	uiExtKeyF11, +	uiExtKeyF12, +	uiExtKeyN0,			// numpad keys; independent of Num Lock state +	uiExtKeyN1,			// N0..N9 are guaranteed to be consecutive +	uiExtKeyN2, +	uiExtKeyN3, +	uiExtKeyN4, +	uiExtKeyN5, +	uiExtKeyN6, +	uiExtKeyN7, +	uiExtKeyN8, +	uiExtKeyN9, +	uiExtKeyNDot, +	uiExtKeyNEnter, +	uiExtKeyNAdd, +	uiExtKeyNSubtract, +	uiExtKeyNMultiply, +	uiExtKeyNDivide, +}; + +struct uiAreaKeyEvent { +	char Key; +	uiExtKey ExtKey; +	uiModifiers Modifier; + +	uiModifiers Modifiers; + +	// additional things +	int Scancode; // bit0-7: scancode, bit8: ext flag + +	int Up; +	int Repeat; +}; + +typedef struct uiFontButton uiFontButton; +#define uiFontButton(this) ((uiFontButton *) (this)) +// TODO document this returns a new font +_UI_EXTERN uiDrawTextFont *uiFontButtonFont(uiFontButton *b); +// TOOD SetFont, mechanics +_UI_EXTERN void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data); +_UI_EXTERN uiFontButton *uiNewFontButton(void); + +typedef struct uiColorButton uiColorButton; +#define uiColorButton(this) ((uiColorButton *) (this)) +_UI_EXTERN void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a); +_UI_EXTERN void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a); +_UI_EXTERN void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data); +_UI_EXTERN uiColorButton *uiNewColorButton(void); + +typedef struct uiForm uiForm; +#define uiForm(this) ((uiForm *) (this)) +_UI_EXTERN void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy); +_UI_EXTERN void uiFormDelete(uiForm *f, int index); +_UI_EXTERN int uiFormPadded(uiForm *f); +_UI_EXTERN void uiFormSetPadded(uiForm *f, int padded); +_UI_EXTERN uiForm *uiNewForm(void); + +_UI_ENUM(uiAlign) { +	uiAlignFill, +	uiAlignStart, +	uiAlignCenter, +	uiAlignEnd, +}; + +_UI_ENUM(uiAt) { +	uiAtLeading, +	uiAtTop, +	uiAtTrailing, +	uiAtBottom, +}; + +typedef struct uiGrid uiGrid; +#define uiGrid(this) ((uiGrid *) (this)) +_UI_EXTERN void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign); +_UI_EXTERN void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign); +_UI_EXTERN int uiGridPadded(uiGrid *g); +_UI_EXTERN void uiGridSetPadded(uiGrid *g, int padded); +_UI_EXTERN uiGrid *uiNewGrid(void); + + +// misc. + +_UI_EXTERN char* uiKeyName(int scancode); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libui_sdl/libui/unix/stddialogs.c b/src/libui_sdl/libui/unix/stddialogs.c new file mode 100644 index 0000000..10c598d --- /dev/null +++ b/src/libui_sdl/libui/unix/stddialogs.c @@ -0,0 +1,126 @@ +// 26 june 2015 +#include "uipriv_unix.h" + +// LONGTERM figure out why, and describe, that this is the desired behavior +// LONGTERM also point out that font and color buttons also work like this + +#define windowWindow(w) ((w)?(GTK_WINDOW(uiControlHandle(uiControl(w)))):NULL) + +static char *filedialog(GtkWindow *parent, GtkFileChooserAction mode, const gchar *confirm, const char* filter, const char* initpath) +{ +	GtkWidget *fcd; +	GtkFileChooser *fc; +	gint response; +	char *filename; + +	fcd = gtk_file_chooser_dialog_new(NULL, parent, mode, +		"_Cancel", GTK_RESPONSE_CANCEL, +		confirm, GTK_RESPONSE_ACCEPT, +		NULL); +	fc = GTK_FILE_CHOOSER(fcd); +	 +	// filters +	{ +		gchar _filter[256]; +        gchar* fp = &_filter[0]; int s = 0; +        gchar* fname; +        for (int i = 0; i < 255; i++) +        { +            if (filter[i] == '|' || filter[i] == '\0') +            { +                _filter[i] = '\0'; +                if (s & 1) +                { +					GtkFileFilter* filter = gtk_file_filter_new(); +					gtk_file_filter_set_name(filter, fname); +					 +					for (gchar* j = fp; ; j++) +					{ +						if (*j == ';') +						{ +						    *j = '\0'; +							gtk_file_filter_add_pattern(filter, fp); +							fp = j+1; +						} +						else if (*j == '\0') +						{ +							gtk_file_filter_add_pattern(filter, fp); +							break; +						} +					} + +					gtk_file_chooser_add_filter(fc, filter); +                } +                else +                { +                    fname = fp; +                } +                fp = &_filter[i+1]; +                s++; +                if (s >= 8) break; +                if (filter[i] == '\0') break; +            } +            else +                _filter[i] = filter[i]; +        } +	} +	 +	gtk_file_chooser_set_local_only(fc, FALSE); +	gtk_file_chooser_set_select_multiple(fc, FALSE); +	gtk_file_chooser_set_show_hidden(fc, TRUE); +	gtk_file_chooser_set_do_overwrite_confirmation(fc, TRUE); +	gtk_file_chooser_set_create_folders(fc, TRUE); +	if (initpath && strlen(initpath)>0)  +	    gtk_file_chooser_set_current_folder(fc, initpath); +	 +	response = gtk_dialog_run(GTK_DIALOG(fcd)); +	if (response != GTK_RESPONSE_ACCEPT) { +		gtk_widget_destroy(fcd); +		return NULL; +	} +	filename = uiUnixStrdupText(gtk_file_chooser_get_filename(fc)); +	gtk_widget_destroy(fcd); +	return filename; +} + +char *uiOpenFile(uiWindow *parent, const char* filter, const char* initpath) +{ +	return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_OPEN, "_Open", filter, initpath); +} + +char *uiSaveFile(uiWindow *parent, const char* filter, const char* initpath) +{ +	return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_SAVE, "_Save", filter, initpath); +} + +static int msgbox(GtkWindow *parent, const char *title, const char *description, GtkMessageType type, GtkButtonsType buttons) +{ +	GtkWidget *md; + +	md = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL, +		type, buttons, +		"%s", title); +	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(md), "%s", description); +	int result = gtk_dialog_run(GTK_DIALOG(md)); +	gtk_widget_destroy(md); + +	return result; +} + +void uiMsgBox(uiWindow *parent, const char *title, const char *description) +{ +	msgbox(windowWindow(parent), title, description, GTK_MESSAGE_OTHER, GTK_BUTTONS_OK); +} + +void uiMsgBoxError(uiWindow *parent, const char *title, const char *description) +{ +	msgbox(windowWindow(parent), title, description, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK); +} + +int uiMsgBoxConfirm(uiWindow * parent, const char *title, const char *description) +{ +	int result = +		msgbox(windowWindow(parent), title, description, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL); + +	return result == GTK_RESPONSE_OK; +}
\ No newline at end of file diff --git a/src/libui_sdl/libui/windows/stddialogs.cpp b/src/libui_sdl/libui/windows/stddialogs.cpp new file mode 100644 index 0000000..7537015 --- /dev/null +++ b/src/libui_sdl/libui/windows/stddialogs.cpp @@ -0,0 +1,180 @@ +// 22 may 2015 +#include "uipriv_windows.hpp" + +// TODO document all this is what we want +// TODO do the same for font and color buttons + +// notes: +// - FOS_SUPPORTSTREAMABLEITEMS doesn't seem to be supported on windows vista, or at least not with the flags we use +// - even with FOS_NOVALIDATE the dialogs will reject invalid filenames (at least on Vista, anyway) +// - lack of FOS_NOREADONLYRETURN doesn't seem to matter on Windows 7 + +// TODO +// - http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx +// - when a dialog is active, tab navigation in other windows stops working +// - when adding uiOpenFolder(), use IFileDialog as well - https://msdn.microsoft.com/en-us/library/windows/desktop/bb762115%28v=vs.85%29.aspx + +#define windowHWND(w) (w ? (HWND) uiControlHandle(uiControl(w)) : NULL) + +char *commonItemDialog(HWND parent, REFCLSID clsid, REFIID iid, const char* filter, const char* initpath, FILEOPENDIALOGOPTIONS optsadd) +{ +	IFileDialog *d = NULL; +	FILEOPENDIALOGOPTIONS opts; +	IShellItem *result = NULL; +	WCHAR *wname = NULL; +	char *name = NULL; +	HRESULT hr; + +	hr = CoCreateInstance(clsid, +		NULL, CLSCTX_INPROC_SERVER, +		iid, (LPVOID *) (&d)); +	if (hr != S_OK) { +		logHRESULT(L"error creating common item dialog", hr); +		// always return NULL on error +		goto out; +	} +	hr = d->GetOptions(&opts); +	if (hr != S_OK) { +		logHRESULT(L"error getting current options", hr); +		goto out; +	} +	opts |= optsadd; +	// the other platforms don't check read-only; we won't either +	opts &= ~FOS_NOREADONLYRETURN; +	hr = d->SetOptions(opts); +	if (hr != S_OK) { +		logHRESULT(L"error setting options", hr); +		goto out; +	} + +	// filters +	{ +        COMDLG_FILTERSPEC filterspec[8]; +        wchar_t _filter[256]; +        wchar_t* fp = &_filter[0]; int s = 0; +        wchar_t* fname; +        for (int i = 0; i < 255; i++) +        { +            if (filter[i] == '|' || filter[i] == '\0') +            { +                _filter[i] = '\0'; +                if (s & 1) +                { +                    filterspec[s>>1].pszName = fname; +                    filterspec[s>>1].pszSpec = fp; +                } +                else +                { +                    fname = fp; +                } +                fp = &_filter[i+1]; +                s++; +                if (s >= 8) break; +                if (filter[i] == '\0') break; +            } +            else +                _filter[i] = filter[i]; +        } +        d->SetFileTypes(s>>1, filterspec); +	} + +	hr = d->Show(parent); +	if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) +		// cancelled; return NULL like we have ready +		goto out; +	if (hr != S_OK) { +		logHRESULT(L"error showing dialog", hr); +		goto out; +	} +	hr = d->GetResult(&result); +	if (hr != S_OK) { +		logHRESULT(L"error getting dialog result", hr); +		goto out; +	} +	hr = result->GetDisplayName(SIGDN_FILESYSPATH, &wname); +	if (hr != S_OK) { +		logHRESULT(L"error getting filename", hr); +		goto out; +	} +	name = toUTF8(wname); + +out: +	if (wname != NULL) +		CoTaskMemFree(wname); +	if (result != NULL) +		result->Release(); +	if (d != NULL) +		d->Release(); +	return name; +} + +char *uiOpenFile(uiWindow *parent, const char* filter, const char* initpath) +{ +	char *res; + +	disableAllWindowsExcept(parent); +	res = commonItemDialog(windowHWND(parent), +		CLSID_FileOpenDialog, IID_IFileOpenDialog, +		filter, initpath, +		FOS_NOCHANGEDIR | FOS_FORCEFILESYSTEM | FOS_NOVALIDATE | FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_SHAREAWARE | FOS_NOTESTFILECREATE | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE); +	enableAllWindowsExcept(parent); +	return res; +} + +char *uiSaveFile(uiWindow *parent, const char* filter, const char* initpath) +{ +	char *res; + +	disableAllWindowsExcept(parent); +	res = commonItemDialog(windowHWND(parent), +		CLSID_FileSaveDialog, IID_IFileSaveDialog, +		filter, initpath, +		FOS_OVERWRITEPROMPT | FOS_NOCHANGEDIR | FOS_FORCEFILESYSTEM | FOS_NOVALIDATE | FOS_SHAREAWARE | FOS_NOTESTFILECREATE | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE); +	enableAllWindowsExcept(parent); +	return res; +} + +// TODO switch to TaskDialogIndirect()? + +static int msgbox(HWND parent, const char *title, const char *description, TASKDIALOG_COMMON_BUTTON_FLAGS buttons, PCWSTR icon) +{ +	WCHAR *wtitle, *wdescription; +	HRESULT hr; + +	wtitle = toUTF16(title); +	wdescription = toUTF16(description); + +	int result; +	hr = TaskDialog(parent, NULL, NULL, wtitle, wdescription, buttons, icon, &result); +	if (hr != S_OK) +		logHRESULT(L"error showing task dialog", hr); + +	uiFree(wdescription); +	uiFree(wtitle); + +	return result; +} + +void uiMsgBox(uiWindow *parent, const char *title, const char *description) +{ +	disableAllWindowsExcept(parent); +	msgbox(windowHWND(parent), title, description, TDCBF_OK_BUTTON, NULL); +	enableAllWindowsExcept(parent); +} + +void uiMsgBoxError(uiWindow *parent, const char *title, const char *description) +{ +	disableAllWindowsExcept(parent); +	msgbox(windowHWND(parent), title, description, TDCBF_OK_BUTTON, TD_ERROR_ICON); +	enableAllWindowsExcept(parent); +} + +int uiMsgBoxConfirm(uiWindow * parent, const char *title, const char *description) +{ +	disableAllWindowsExcept(parent); +	int result = +		msgbox(windowHWND(parent), title, description, TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON, TD_WARNING_ICON); +	enableAllWindowsExcept(parent); + +	return result == IDOK; +}
\ No newline at end of file diff --git a/src/libui_sdl/main.cpp b/src/libui_sdl/main.cpp new file mode 100644 index 0000000..0066668 --- /dev/null +++ b/src/libui_sdl/main.cpp @@ -0,0 +1,3061 @@ +/* +    Copyright 2016-2020 Arisotura + +    This file is part of melonDS. + +    melonDS is free software: you can redistribute it and/or modify it under +    the terms of the GNU General Public License as published by the Free +    Software Foundation, either version 3 of the License, or (at your option) +    any later version. + +    melonDS is distributed in the hope that it will be useful, but WITHOUT ANY +    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +    FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License along +    with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdlib.h> +#include <time.h> +#include <stdio.h> +#include <string.h> + +#ifndef __WIN32__ +#include <glib.h> +#endif + +#include <SDL2/SDL.h> +#include "libui/ui.h" + +#include "../OpenGLSupport.h" +#include "main_shaders.h" + +#include "../types.h" +#include "../version.h" +#include "PlatformConfig.h" + +#include "DlgEmuSettings.h" +#include "DlgInputConfig.h" +#include "DlgVideoSettings.h" +#include "DlgAudioSettings.h" +#include "DlgWifiSettings.h" + +#include "../NDS.h" +#include "../GBACart.h" +#include "../GPU.h" +#include "../SPU.h" +#include "../Wifi.h" +#include "../Platform.h" +#include "../Config.h" +#include "../ARMJIT.h" + +#include "../Savestate.h" + +#include "OSD.h" + +#ifdef MELONCAP +#include "MelonCap.h" +#endif // MELONCAP + + +// savestate slot mapping +// 1-8: regular slots (quick access) +// '9': load/save arbitrary file +const int kSavestateNum[9] = {1, 2, 3, 4, 5, 6, 7, 8, 0}; + +const int kScreenSize[4] = {1, 2, 3, 4}; +const int kScreenRot[4] = {0, 1, 2, 3}; +const int kScreenGap[6] = {0, 1, 8, 64, 90, 128}; +const int kScreenLayout[3] = {0, 1, 2}; +const int kScreenSizing[4] = {0, 1, 2, 3}; + + +char* EmuDirectory; + + +uiWindow* MainWindow; +uiArea* MainDrawArea; +uiAreaHandler MainDrawAreaHandler; + +const u32 kGLVersions[] = {uiGLVersion(3,2), uiGLVersion(3,1), 0}; +uiGLContext* GLContext; + +int WindowWidth, WindowHeight; + +uiMenuItem* MenuItem_SaveState; +uiMenuItem* MenuItem_LoadState; +uiMenuItem* MenuItem_UndoStateLoad; + +uiMenuItem* MenuItem_SaveStateSlot[9]; +uiMenuItem* MenuItem_LoadStateSlot[9]; + +uiMenuItem* MenuItem_Pause; +uiMenuItem* MenuItem_Reset; +uiMenuItem* MenuItem_Stop; + +uiMenuItem* MenuItem_SavestateSRAMReloc; + +uiMenuItem* MenuItem_ScreenRot[4]; +uiMenuItem* MenuItem_ScreenGap[6]; +uiMenuItem* MenuItem_ScreenLayout[3]; +uiMenuItem* MenuItem_ScreenSizing[4]; + +uiMenuItem* MenuItem_ScreenFilter; +uiMenuItem* MenuItem_LimitFPS; +uiMenuItem* MenuItem_AudioSync; +uiMenuItem* MenuItem_ShowOSD; + +SDL_Thread* EmuThread; +int EmuRunning; +volatile int EmuStatus; + +bool RunningSomething; +char ROMPath[2][1024]; +char SRAMPath[2][1024]; +char PrevSRAMPath[2][1024]; // for savestate 'undo load' + +bool SavestateLoaded; + +bool Screen_UseGL; + +bool ScreenDrawInited = false; +uiDrawBitmap* ScreenBitmap[2] = {NULL,NULL}; + +GLuint GL_ScreenShader[3]; +GLuint GL_ScreenShaderAccel[3]; +GLuint GL_ScreenShaderOSD[3]; +struct +{ +    float uScreenSize[2]; +    u32 u3DScale; +    u32 uFilterMode; + +} GL_ShaderConfig; +GLuint GL_ShaderConfigUBO; +GLuint GL_ScreenVertexArrayID, GL_ScreenVertexBufferID; +float GL_ScreenVertices[2 * 3*2 * 4]; // position/texcoord +GLuint GL_ScreenTexture; +bool GL_ScreenSizeDirty; + +int GL_3DScale; + +bool GL_VSyncStatus; + +int ScreenGap = 0; +int ScreenLayout = 0; +int ScreenSizing = 0; +int ScreenRotation = 0; + +int MainScreenPos[3]; +int AutoScreenSizing; + +uiRect TopScreenRect; +uiRect BottomScreenRect; +uiDrawMatrix TopScreenTrans; +uiDrawMatrix BottomScreenTrans; + +bool Touching = false; + +u32 KeyInputMask, JoyInputMask; +u32 KeyHotkeyMask, JoyHotkeyMask; +u32 HotkeyMask, LastHotkeyMask; +u32 HotkeyPress, HotkeyRelease; + +#define HotkeyDown(hk)     (HotkeyMask & (1<<(hk))) +#define HotkeyPressed(hk)  (HotkeyPress & (1<<(hk))) +#define HotkeyReleased(hk) (HotkeyRelease & (1<<(hk))) + +bool LidStatus; + +int JoystickID; +SDL_Joystick* Joystick; + +int AudioFreq; +float AudioSampleFrac; +SDL_AudioDeviceID AudioDevice, MicDevice; + +SDL_cond* AudioSync; +SDL_mutex* AudioSyncLock; + +u32 MicBufferLength = 2048; +s16 MicBuffer[2048]; +u32 MicBufferReadPos, MicBufferWritePos; + +u32 MicWavLength; +s16* MicWavBuffer; + +void SetupScreenRects(int width, int height); + +void TogglePause(void* blarg); +void Reset(void* blarg); + +void SetupSRAMPath(int slot); + +void SaveState(int slot); +void LoadState(int slot); +void UndoStateLoad(); +void GetSavestateName(int slot, char* filename, int len); + +void CreateMainWindow(bool opengl); +void DestroyMainWindow(); +void RecreateMainWindow(bool opengl); + + + +bool GLScreen_InitShader(GLuint* shader, const char* fs) +{ +    if (!OpenGL_BuildShaderProgram(kScreenVS, fs, shader, "ScreenShader")) +        return false; + +    glBindAttribLocation(shader[2], 0, "vPosition"); +    glBindAttribLocation(shader[2], 1, "vTexcoord"); +    glBindFragDataLocation(shader[2], 0, "oColor"); + +    if (!OpenGL_LinkShaderProgram(shader)) +        return false; + +    GLuint uni_id; + +    uni_id = glGetUniformBlockIndex(shader[2], "uConfig"); +    glUniformBlockBinding(shader[2], uni_id, 16); + +    glUseProgram(shader[2]); +    uni_id = glGetUniformLocation(shader[2], "ScreenTex"); +    glUniform1i(uni_id, 0); +    uni_id = glGetUniformLocation(shader[2], "_3DTex"); +    glUniform1i(uni_id, 1); + +    return true; +} + +bool GLScreen_InitOSDShader(GLuint* shader) +{ +    if (!OpenGL_BuildShaderProgram(kScreenVS_OSD, kScreenFS_OSD, shader, "ScreenShaderOSD")) +        return false; + +    glBindAttribLocation(shader[2], 0, "vPosition"); +    glBindFragDataLocation(shader[2], 0, "oColor"); + +    if (!OpenGL_LinkShaderProgram(shader)) +        return false; + +    GLuint uni_id; + +    uni_id = glGetUniformBlockIndex(shader[2], "uConfig"); +    glUniformBlockBinding(shader[2], uni_id, 16); + +    glUseProgram(shader[2]); +    uni_id = glGetUniformLocation(shader[2], "OSDTex"); +    glUniform1i(uni_id, 0); + +    return true; +} + +bool GLScreen_Init() +{ +    GL_VSyncStatus = Config::ScreenVSync; + +    // TODO: consider using epoxy? +    if (!OpenGL_Init()) +        return false; + +    const GLubyte* renderer = glGetString(GL_RENDERER); // get renderer string +    const GLubyte* version = glGetString(GL_VERSION); // version as a string +    printf("OpenGL: renderer: %s\n", renderer); +    printf("OpenGL: version: %s\n", version); + +    if (!GLScreen_InitShader(GL_ScreenShader, kScreenFS)) +        return false; +    if (!GLScreen_InitShader(GL_ScreenShaderAccel, kScreenFS_Accel)) +        return false; +    if (!GLScreen_InitOSDShader(GL_ScreenShaderOSD)) +        return false; + +    memset(&GL_ShaderConfig, 0, sizeof(GL_ShaderConfig)); + +    glGenBuffers(1, &GL_ShaderConfigUBO); +    glBindBuffer(GL_UNIFORM_BUFFER, GL_ShaderConfigUBO); +    glBufferData(GL_UNIFORM_BUFFER, sizeof(GL_ShaderConfig), &GL_ShaderConfig, GL_STATIC_DRAW); +    glBindBufferBase(GL_UNIFORM_BUFFER, 16, GL_ShaderConfigUBO); + +    glGenBuffers(1, &GL_ScreenVertexBufferID); +    glBindBuffer(GL_ARRAY_BUFFER, GL_ScreenVertexBufferID); +    glBufferData(GL_ARRAY_BUFFER, sizeof(GL_ScreenVertices), NULL, GL_STATIC_DRAW); + +    glGenVertexArrays(1, &GL_ScreenVertexArrayID); +    glBindVertexArray(GL_ScreenVertexArrayID); +    glEnableVertexAttribArray(0); // position +    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0)); +    glEnableVertexAttribArray(1); // texcoord +    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4)); + +    glGenTextures(1, &GL_ScreenTexture); +    glActiveTexture(GL_TEXTURE0); +    glBindTexture(GL_TEXTURE_2D, GL_ScreenTexture); +    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); +    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); +    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); +    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, 256*3 + 1, 192*2, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, NULL); + +    GL_ScreenSizeDirty = true; + +    return true; +} + +void GLScreen_DeInit() +{ +    glDeleteTextures(1, &GL_ScreenTexture); + +    glDeleteVertexArrays(1, &GL_ScreenVertexArrayID); +    glDeleteBuffers(1, &GL_ScreenVertexBufferID); + +    OpenGL_DeleteShaderProgram(GL_ScreenShader); +    OpenGL_DeleteShaderProgram(GL_ScreenShaderAccel); +    OpenGL_DeleteShaderProgram(GL_ScreenShaderOSD); +} + +void GLScreen_DrawScreen() +{ +    bool vsync = Config::ScreenVSync && !HotkeyDown(HK_FastForward); +    if (vsync != GL_VSyncStatus) +    { +        GL_VSyncStatus = vsync; +        uiGLSetVSync(vsync); +    } + +    float scale = uiGLGetFramebufferScale(GLContext); + +    glBindFramebuffer(GL_FRAMEBUFFER, uiGLGetFramebuffer(GLContext)); + +    if (GL_ScreenSizeDirty) +    { +        GL_ScreenSizeDirty = false; + +        GL_ShaderConfig.uScreenSize[0] = WindowWidth; +        GL_ShaderConfig.uScreenSize[1] = WindowHeight; +        GL_ShaderConfig.u3DScale = GL_3DScale; + +        glBindBuffer(GL_UNIFORM_BUFFER, GL_ShaderConfigUBO); +        void* unibuf = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY); +        if (unibuf) memcpy(unibuf, &GL_ShaderConfig, sizeof(GL_ShaderConfig)); +        glUnmapBuffer(GL_UNIFORM_BUFFER); + +        float scwidth, scheight; + +        float x0, y0, x1, y1; +        float s0, s1, s2, s3; +        float t0, t1, t2, t3; + +#define SETVERTEX(i, x, y, s, t) \ +    GL_ScreenVertices[4*(i) + 0] = x; \ +    GL_ScreenVertices[4*(i) + 1] = y; \ +    GL_ScreenVertices[4*(i) + 2] = s; \ +    GL_ScreenVertices[4*(i) + 3] = t; + +        x0 = TopScreenRect.X; +        y0 = TopScreenRect.Y; +        x1 = TopScreenRect.X + TopScreenRect.Width; +        y1 = TopScreenRect.Y + TopScreenRect.Height; + +        scwidth = 256; +        scheight = 192; + +        switch (ScreenRotation) +        { +        case 0: +            s0 = 0; t0 = 0; +            s1 = scwidth; t1 = 0; +            s2 = 0; t2 = scheight; +            s3 = scwidth; t3 = scheight; +            break; + +        case 1: +            s0 = 0; t0 = scheight; +            s1 = 0; t1 = 0; +            s2 = scwidth; t2 = scheight; +            s3 = scwidth; t3 = 0; +            break; + +        case 2: +            s0 = scwidth; t0 = scheight; +            s1 = 0; t1 = scheight; +            s2 = scwidth; t2 = 0; +            s3 = 0; t3 = 0; +            break; + +        case 3: +            s0 = scwidth; t0 = 0; +            s1 = scwidth; t1 = scheight; +            s2 = 0; t2 = 0; +            s3 = 0; t3 = scheight; +            break; +        } + +        SETVERTEX(0, x0, y0, s0, t0); +        SETVERTEX(1, x1, y1, s3, t3); +        SETVERTEX(2, x1, y0, s1, t1); +        SETVERTEX(3, x0, y0, s0, t0); +        SETVERTEX(4, x0, y1, s2, t2); +        SETVERTEX(5, x1, y1, s3, t3); + +        x0 = BottomScreenRect.X; +        y0 = BottomScreenRect.Y; +        x1 = BottomScreenRect.X + BottomScreenRect.Width; +        y1 = BottomScreenRect.Y + BottomScreenRect.Height; + +        scwidth = 256; +        scheight = 192; + +        switch (ScreenRotation) +        { +        case 0: +            s0 = 0; t0 = 192; +            s1 = scwidth; t1 = 192; +            s2 = 0; t2 = 192+scheight; +            s3 = scwidth; t3 = 192+scheight; +            break; + +        case 1: +            s0 = 0; t0 = 192+scheight; +            s1 = 0; t1 = 192; +            s2 = scwidth; t2 = 192+scheight; +            s3 = scwidth; t3 = 192; +            break; + +        case 2: +            s0 = scwidth; t0 = 192+scheight; +            s1 = 0; t1 = 192+scheight; +            s2 = scwidth; t2 = 192; +            s3 = 0; t3 = 192; +            break; + +        case 3: +            s0 = scwidth; t0 = 192; +            s1 = scwidth; t1 = 192+scheight; +            s2 = 0; t2 = 192; +            s3 = 0; t3 = 192+scheight; +            break; +        } + +        SETVERTEX(6, x0, y0, s0, t0); +        SETVERTEX(7, x1, y1, s3, t3); +        SETVERTEX(8, x1, y0, s1, t1); +        SETVERTEX(9, x0, y0, s0, t0); +        SETVERTEX(10, x0, y1, s2, t2); +        SETVERTEX(11, x1, y1, s3, t3); + +#undef SETVERTEX + +        glBindBuffer(GL_ARRAY_BUFFER, GL_ScreenVertexBufferID); +        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GL_ScreenVertices), GL_ScreenVertices); +    } + +    glDisable(GL_DEPTH_TEST); +    glDisable(GL_STENCIL_TEST); +    glDisable(GL_BLEND); +    glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + +    glViewport(0, 0, WindowWidth*scale, WindowHeight*scale); + +    if (GPU3D::Renderer == 0) +        OpenGL_UseShaderProgram(GL_ScreenShader); +    else +        OpenGL_UseShaderProgram(GL_ScreenShaderAccel); + +    glClearColor(0, 0, 0, 1); +    glClear(GL_COLOR_BUFFER_BIT); + +    if (RunningSomething) +    { +        int frontbuf = GPU::FrontBuffer; +        glActiveTexture(GL_TEXTURE0); +        glBindTexture(GL_TEXTURE_2D, GL_ScreenTexture); + +        if (GPU::Framebuffer[frontbuf][0] && GPU::Framebuffer[frontbuf][1]) +        { +            if (GPU3D::Renderer == 0) +            { +                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA_INTEGER, +                                GL_UNSIGNED_BYTE, GPU::Framebuffer[frontbuf][0]); +                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 192, GL_RGBA_INTEGER, +                                GL_UNSIGNED_BYTE, GPU::Framebuffer[frontbuf][1]); +            } +            else +            { +                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256*3 + 1, 192, GL_RGBA_INTEGER, +                                GL_UNSIGNED_BYTE, GPU::Framebuffer[frontbuf][0]); +                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256*3 + 1, 192, GL_RGBA_INTEGER, +                                GL_UNSIGNED_BYTE, GPU::Framebuffer[frontbuf][1]); +            } +        } + +        glActiveTexture(GL_TEXTURE1); +        if (GPU3D::Renderer != 0) +            GPU3D::GLRenderer::SetupAccelFrame(); + +        glBindBuffer(GL_ARRAY_BUFFER, GL_ScreenVertexBufferID); +        glBindVertexArray(GL_ScreenVertexArrayID); +        glDrawArrays(GL_TRIANGLES, 0, 4*3); +    } + +    OpenGL_UseShaderProgram(GL_ScreenShaderOSD); +    OSD::Update(true, NULL); + +    glFlush(); +    uiGLSwapBuffers(GLContext); +} + +void MicLoadWav(char* name) +{ +    SDL_AudioSpec format; +    memset(&format, 0, sizeof(SDL_AudioSpec)); + +    if (MicWavBuffer) delete[] MicWavBuffer; +    MicWavBuffer = NULL; +    MicWavLength = 0; + +    u8* buf; +    u32 len; +    if (!SDL_LoadWAV(name, &format, &buf, &len)) +        return; + +    const u64 dstfreq = 44100; + +    if (format.format == AUDIO_S16 || format.format == AUDIO_U16) +    { +        int srcinc = format.channels; +        len /= (2 * srcinc); + +        MicWavLength = (len * dstfreq) / format.freq; +        if (MicWavLength < 735) MicWavLength = 735; +        MicWavBuffer = new s16[MicWavLength]; + +        float res_incr = len / (float)MicWavLength; +        float res_timer = 0; +        int res_pos = 0; + +        for (int i = 0; i < MicWavLength; i++) +        { +            u16 val = ((u16*)buf)[res_pos]; +            if (SDL_AUDIO_ISUNSIGNED(format.format)) val ^= 0x8000; + +            MicWavBuffer[i] = val; + +            res_timer += res_incr; +            while (res_timer >= 1.0) +            { +                res_timer -= 1.0; +                res_pos += srcinc; +            } +        } +    } +    else if (format.format == AUDIO_S8 || format.format == AUDIO_U8) +    { +        int srcinc = format.channels; +        len /= srcinc; + +        MicWavLength = (len * dstfreq) / format.freq; +        if (MicWavLength < 735) MicWavLength = 735; +        MicWavBuffer = new s16[MicWavLength]; + +        float res_incr = len / (float)MicWavLength; +        float res_timer = 0; +        int res_pos = 0; + +        for (int i = 0; i < MicWavLength; i++) +        { +            u16 val = buf[res_pos] << 8; +            if (SDL_AUDIO_ISUNSIGNED(format.format)) val ^= 0x8000; + +            MicWavBuffer[i] = val; + +            res_timer += res_incr; +            while (res_timer >= 1.0) +            { +                res_timer -= 1.0; +                res_pos += srcinc; +            } +        } +    } +    else +        printf("bad WAV format %08X\n", format.format); + +    SDL_FreeWAV(buf); +} + +void AudioCallback(void* data, Uint8* stream, int len) +{ +    len /= (sizeof(s16) * 2); + +    // resample incoming audio to match the output sample rate + +    float f_len_in = (len * 32823.6328125) / (float)AudioFreq; +    f_len_in += AudioSampleFrac; +    int len_in = (int)floor(f_len_in); +    AudioSampleFrac = f_len_in - len_in; + +    s16 buf_in[1024*2]; +    s16* buf_out = (s16*)stream; + +    int num_in; +    int num_out = len; + +    SDL_LockMutex(AudioSyncLock); +    num_in = SPU::ReadOutput(buf_in, len_in); +    SDL_CondSignal(AudioSync); +    SDL_UnlockMutex(AudioSyncLock); + +    if (num_in < 1) +    { +        memset(stream, 0, len*sizeof(s16)*2); +        return; +    } + +    int margin = 6; +    if (num_in < len_in-margin) +    { +        int last = num_in-1; +        if (last < 0) last = 0; + +        for (int i = num_in; i < len_in-margin; i++) +            ((u32*)buf_in)[i] = ((u32*)buf_in)[last]; + +        num_in = len_in-margin; +    } + +    float res_incr = num_in / (float)num_out; +    float res_timer = 0; +    int res_pos = 0; + +    int volume = Config::AudioVolume; + +    for (int i = 0; i < len; i++) +    { +        buf_out[i*2  ] = (buf_in[res_pos*2  ] * volume) >> 8; +        buf_out[i*2+1] = (buf_in[res_pos*2+1] * volume) >> 8; + +        /*s16 s_l = buf_in[res_pos*2  ]; +        s16 s_r = buf_in[res_pos*2+1]; + +        float a = res_timer; +        float b = 1.0 - a; +        s_l = (s_l * a) + (buf_in[(res_pos-1)*2  ] * b); +        s_r = (s_r * a) + (buf_in[(res_pos-1)*2+1] * b); + +        buf_out[i*2  ] = (s_l * volume) >> 8; +        buf_out[i*2+1] = (s_r * volume) >> 8;*/ + +        res_timer += res_incr; +        while (res_timer >= 1.0) +        { +            res_timer -= 1.0; +            res_pos++; +        } +    } +} + +void MicCallback(void* data, Uint8* stream, int len) +{ +    if (Config::MicInputType != 1) return; + +    s16* input = (s16*)stream; +    len /= sizeof(s16); + +    if ((MicBufferWritePos + len) > MicBufferLength) +    { +        u32 len1 = MicBufferLength - MicBufferWritePos; +        memcpy(&MicBuffer[MicBufferWritePos], &input[0], len1*sizeof(s16)); +        memcpy(&MicBuffer[0], &input[len1], (len - len1)*sizeof(s16)); +        MicBufferWritePos = len - len1; +    } +    else +    { +        memcpy(&MicBuffer[MicBufferWritePos], input, len*sizeof(s16)); +        MicBufferWritePos += len; +    } +} + +void FeedMicInput() +{ +    int type = Config::MicInputType; +    bool cmd = HotkeyDown(HK_Mic); + +    if ((type != 1 && !cmd) || +        (type == 1 && MicBufferLength == 0) || +        (type == 3 && MicWavBuffer == NULL)) +    { +        type = 0; +        MicBufferReadPos = 0; +    } + +    switch (type) +    { +    case 0: // no mic +        NDS::MicInputFrame(NULL, 0); +        break; + +    case 1: // host mic +        if ((MicBufferReadPos + 735) > MicBufferLength) +        { +            s16 tmp[735]; +            u32 len1 = MicBufferLength - MicBufferReadPos; +            memcpy(&tmp[0], &MicBuffer[MicBufferReadPos], len1*sizeof(s16)); +            memcpy(&tmp[len1], &MicBuffer[0], (735 - len1)*sizeof(s16)); + +            NDS::MicInputFrame(tmp, 735); +            MicBufferReadPos = 735 - len1; +        } +        else +        { +            NDS::MicInputFrame(&MicBuffer[MicBufferReadPos], 735); +            MicBufferReadPos += 735; +        } +        break; + +    case 2: // white noise +        { +            s16 tmp[735]; +            for (int i = 0; i < 735; i++) tmp[i] = rand() & 0xFFFF; +            NDS::MicInputFrame(tmp, 735); +        } +        break; + +    case 3: // WAV +        if ((MicBufferReadPos + 735) > MicWavLength) +        { +            s16 tmp[735]; +            u32 len1 = MicWavLength - MicBufferReadPos; +            memcpy(&tmp[0], &MicWavBuffer[MicBufferReadPos], len1*sizeof(s16)); +            memcpy(&tmp[len1], &MicWavBuffer[0], (735 - len1)*sizeof(s16)); + +            NDS::MicInputFrame(tmp, 735); +            MicBufferReadPos = 735 - len1; +        } +        else +        { +            NDS::MicInputFrame(&MicWavBuffer[MicBufferReadPos], 735); +            MicBufferReadPos += 735; +        } +        break; +    } +} + +void OpenJoystick() +{ +    if (Joystick) SDL_JoystickClose(Joystick); + +    int num = SDL_NumJoysticks(); +    if (num < 1) +    { +        Joystick = NULL; +        return; +    } + +    if (JoystickID >= num) +        JoystickID = 0; + +    Joystick = SDL_JoystickOpen(JoystickID); +} + +bool JoystickButtonDown(int val) +{ +    if (val == -1) return false; + +    bool hasbtn = ((val & 0xFFFF) != 0xFFFF); + +    if (hasbtn) +    { +        if (val & 0x100) +        { +            int hatnum = (val >> 4) & 0xF; +            int hatdir = val & 0xF; +            Uint8 hatval = SDL_JoystickGetHat(Joystick, hatnum); + +            bool pressed = false; +            if      (hatdir == 0x1) pressed = (hatval & SDL_HAT_UP); +            else if (hatdir == 0x4) pressed = (hatval & SDL_HAT_DOWN); +            else if (hatdir == 0x2) pressed = (hatval & SDL_HAT_RIGHT); +            else if (hatdir == 0x8) pressed = (hatval & SDL_HAT_LEFT); + +            if (pressed) return true; +        } +        else +        { +            int btnnum = val & 0xFFFF; +            Uint8 btnval = SDL_JoystickGetButton(Joystick, btnnum); + +            if (btnval) return true; +        } +    } + +    if (val & 0x10000) +    { +        int axisnum = (val >> 24) & 0xF; +        int axisdir = (val >> 20) & 0xF; +        Sint16 axisval = SDL_JoystickGetAxis(Joystick, axisnum); + +        switch (axisdir) +        { +        case 0: // positive +            if (axisval > 16384) return true; +            break; + +        case 1: // negative +            if (axisval < -16384) return true; +            break; + +        case 2: // trigger +            if (axisval > 0) return true; +            break; +        } +    } + +    return false; +} + +void ProcessInput() +{ +    SDL_JoystickUpdate(); + +    if (Joystick) +    { +        if (!SDL_JoystickGetAttached(Joystick)) +        { +            SDL_JoystickClose(Joystick); +            Joystick = NULL; +        } +    } +    if (!Joystick && (SDL_NumJoysticks() > 0)) +    { +        JoystickID = Config::JoystickID; +        OpenJoystick(); +    } + +    JoyInputMask = 0xFFF; +    for (int i = 0; i < 12; i++) +        if (JoystickButtonDown(Config::JoyMapping[i])) +            JoyInputMask &= ~(1<<i); + +    JoyHotkeyMask = 0; +    for (int i = 0; i < HK_MAX; i++) +        if (JoystickButtonDown(Config::HKJoyMapping[i])) +            JoyHotkeyMask |= (1<<i); + +    HotkeyMask = KeyHotkeyMask | JoyHotkeyMask; +    HotkeyPress = HotkeyMask & ~LastHotkeyMask; +    HotkeyRelease = LastHotkeyMask & ~HotkeyMask; +    LastHotkeyMask = HotkeyMask; +} + +bool JoyButtonPressed(int btnid, int njoybuttons, Uint8* joybuttons, Uint32 hat) +{ +    if (btnid < 0) return false; + +    hat &= ~(hat >> 4); + +    bool pressed = false; +    if (btnid == 0x101) // up +        pressed = (hat & SDL_HAT_UP); +    else if (btnid == 0x104) // down +        pressed = (hat & SDL_HAT_DOWN); +    else if (btnid == 0x102) // right +        pressed = (hat & SDL_HAT_RIGHT); +    else if (btnid == 0x108) // left +        pressed = (hat & SDL_HAT_LEFT); +    else if (btnid < njoybuttons) +        pressed = (joybuttons[btnid] & ~(joybuttons[btnid] >> 1)) & 0x01; + +    return pressed; +} + +bool JoyButtonHeld(int btnid, int njoybuttons, Uint8* joybuttons, Uint32 hat) +{ +    if (btnid < 0) return false; + +    bool pressed = false; +    if (btnid == 0x101) // up +        pressed = (hat & SDL_HAT_UP); +    else if (btnid == 0x104) // down +        pressed = (hat & SDL_HAT_DOWN); +    else if (btnid == 0x102) // right +        pressed = (hat & SDL_HAT_RIGHT); +    else if (btnid == 0x108) // left +        pressed = (hat & SDL_HAT_LEFT); +    else if (btnid < njoybuttons) +        pressed = joybuttons[btnid] & 0x01; + +    return pressed; +} + +void UpdateWindowTitle(void* data) +{ +    if (EmuStatus == 0) return; +    void** dataarray = (void**)data; +    SDL_LockMutex((SDL_mutex*)dataarray[1]); +    uiWindowSetTitle(MainWindow, (const char*)dataarray[0]); +    SDL_UnlockMutex((SDL_mutex*)dataarray[1]); +} + +void UpdateFPSLimit(void* data) +{ +    uiMenuItemSetChecked(MenuItem_LimitFPS, Config::LimitFPS==1); +} + +int EmuThreadFunc(void* burp) +{ +    NDS::Init(); + +    MainScreenPos[0] = 0; +    MainScreenPos[1] = 0; +    MainScreenPos[2] = 0; +    AutoScreenSizing = 0; + +    if (Screen_UseGL) +    { +        uiGLMakeContextCurrent(GLContext); +        GPU3D::InitRenderer(true); +        uiGLMakeContextCurrent(NULL); +    } +    else +    { +        GPU3D::InitRenderer(false); +    } + +    Touching = false; +    KeyInputMask = 0xFFF; +    JoyInputMask = 0xFFF; +    KeyHotkeyMask = 0; +    JoyHotkeyMask = 0; +    HotkeyMask = 0; +    LastHotkeyMask = 0; +    LidStatus = false; + +    u32 nframes = 0; +    u32 starttick = SDL_GetTicks(); +    u32 lasttick = starttick; +    u32 lastmeasuretick = lasttick; +    u32 fpslimitcount = 0; +    u64 perfcount = SDL_GetPerformanceCounter(); +    u64 perffreq = SDL_GetPerformanceFrequency(); +    float samplesleft = 0; +    u32 nsamples = 0; + +    char melontitle[100]; +    SDL_mutex* titlemutex = SDL_CreateMutex(); +    void* titledata[2] = {melontitle, titlemutex}; + +    while (EmuRunning != 0) +    { +        ProcessInput(); + +        if (HotkeyPressed(HK_FastForwardToggle)) +        { +            Config::LimitFPS = !Config::LimitFPS; +            uiQueueMain(UpdateFPSLimit, NULL); +        } +        // TODO: similar hotkeys for video/audio sync? + +        if (HotkeyPressed(HK_Pause)) uiQueueMain(TogglePause, NULL); +        if (HotkeyPressed(HK_Reset)) uiQueueMain(Reset, NULL); + +        if (GBACart::CartInserted && GBACart::HasSolarSensor) +        { +            if (HotkeyPressed(HK_SolarSensorDecrease)) +            { +                if (GBACart_SolarSensor::LightLevel > 0) GBACart_SolarSensor::LightLevel--; +                char msg[64]; +                sprintf(msg, "Solar sensor level set to %d", GBACart_SolarSensor::LightLevel); +                OSD::AddMessage(0, msg); +            } +            if (HotkeyPressed(HK_SolarSensorIncrease)) +            { +                if (GBACart_SolarSensor::LightLevel < 10) GBACart_SolarSensor::LightLevel++; +                char msg[64]; +                sprintf(msg, "Solar sensor level set to %d", GBACart_SolarSensor::LightLevel); +                OSD::AddMessage(0, msg); +            } +        } + +        if (EmuRunning == 1) +        { +            EmuStatus = 1; + +            // process input and hotkeys +            NDS::SetKeyMask(KeyInputMask & JoyInputMask); + +            if (HotkeyPressed(HK_Lid)) +            { +                LidStatus = !LidStatus; +                NDS::SetLidClosed(LidStatus); +                OSD::AddMessage(0, LidStatus ? "Lid closed" : "Lid opened"); +            } + +            // microphone input +            FeedMicInput(); + +            if (Screen_UseGL) +            { +                uiGLBegin(GLContext); +                uiGLMakeContextCurrent(GLContext); +            } + +            // auto screen layout +            { +                MainScreenPos[2] = MainScreenPos[1]; +                MainScreenPos[1] = MainScreenPos[0]; +                MainScreenPos[0] = NDS::PowerControl9 >> 15; + +                int guess; +                if (MainScreenPos[0] == MainScreenPos[2] && +                    MainScreenPos[0] != MainScreenPos[1]) +                { +                    // constant flickering, likely displaying 3D on both screens +                    // TODO: when both screens are used for 2D only...??? +                    guess = 0; +                } +                else +                { +                    if (MainScreenPos[0] == 1) +                        guess = 1; +                    else +                        guess = 2; +                } + +                if (guess != AutoScreenSizing) +                { +                    AutoScreenSizing = guess; +                    SetupScreenRects(WindowWidth, WindowHeight); +                } +            } + +            // emulate +            u32 nlines = NDS::RunFrame(); + +#ifdef MELONCAP +            MelonCap::Update(); +#endif // MELONCAP + +            if (EmuRunning == 0) break; + +            if (Screen_UseGL) +            { +                GLScreen_DrawScreen(); +                uiGLEnd(GLContext); +            } +            uiAreaQueueRedrawAll(MainDrawArea); + +            bool fastforward = HotkeyDown(HK_FastForward); + +            if (Config::AudioSync && !fastforward) +            { +                SDL_LockMutex(AudioSyncLock); +                while (SPU::GetOutputSize() > 1024) +                { +                    int ret = SDL_CondWaitTimeout(AudioSync, AudioSyncLock, 500); +                    if (ret == SDL_MUTEX_TIMEDOUT) break; +                } +                SDL_UnlockMutex(AudioSyncLock); +            } +            else +            { +                // ensure the audio FIFO doesn't overflow +                //SPU::TrimOutput(); +            } + +            float framerate = (1000.0f * nlines) / (60.0f * 263.0f); + +            { +                u32 curtick = SDL_GetTicks(); +                u32 delay = curtick - lasttick; + +                bool limitfps = Config::LimitFPS && !fastforward; +                if (limitfps) +                { +                    float wantedtickF = starttick + (framerate * (fpslimitcount+1)); +                    u32 wantedtick = (u32)ceil(wantedtickF); +                    if (curtick < wantedtick) SDL_Delay(wantedtick - curtick); + +                    lasttick = SDL_GetTicks(); +                    fpslimitcount++; +                    if ((abs(wantedtickF - (float)wantedtick) < 0.001312) || (fpslimitcount > 60)) +                    { +                        fpslimitcount = 0; +                        nsamples = 0; +                        starttick = lasttick; +                    } +                } +                else +                { +                    if (delay < 1) SDL_Delay(1); +                    lasttick = SDL_GetTicks(); +                } +            } + +            nframes++; +            if (nframes >= 30) +            { +                u32 tick = SDL_GetTicks(); +                u32 diff = tick - lastmeasuretick; +                lastmeasuretick = tick; + +                u32 fps; +                if (diff < 1) fps = 77777; +                else fps = (nframes * 1000) / diff; +                nframes = 0; + +                float fpstarget; +                if (framerate < 1) fpstarget = 999; +                else fpstarget = 1000.0f/framerate; + +                SDL_LockMutex(titlemutex); +                sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); +                SDL_UnlockMutex(titlemutex); +                uiQueueMain(UpdateWindowTitle, titledata); +            } +        } +        else +        { +            // paused +            nframes = 0; +            lasttick = SDL_GetTicks(); +            starttick = lasttick; +            lastmeasuretick = lasttick; +            fpslimitcount = 0; + +            if (EmuRunning == 2) +            { +                if (Screen_UseGL) +                { +                    uiGLBegin(GLContext); +                    uiGLMakeContextCurrent(GLContext); +                    GLScreen_DrawScreen(); +                    uiGLEnd(GLContext); +                } +                uiAreaQueueRedrawAll(MainDrawArea); +            } + +            if (Screen_UseGL) uiGLMakeContextCurrent(NULL); + +            EmuStatus = EmuRunning; + +            SDL_Delay(100); +        } +    } + +    EmuStatus = 0; + +    SDL_DestroyMutex(titlemutex); + +    if (Screen_UseGL) uiGLMakeContextCurrent(GLContext); + +    NDS::DeInit(); +    Platform::LAN_DeInit(); + +    if (Screen_UseGL) +    { +        OSD::DeInit(true); +        GLScreen_DeInit(); +    } +    else +        OSD::DeInit(false); + +    if (Screen_UseGL) uiGLMakeContextCurrent(NULL); + +    return 44203; +} + +void StopEmuThread() +{ +    EmuRunning = 0; +    SDL_WaitThread(EmuThread, NULL); +} + + +void OnAreaDraw(uiAreaHandler* handler, uiArea* area, uiAreaDrawParams* params) +{ +    if (!ScreenDrawInited) +    { +        if (ScreenBitmap[0]) uiDrawFreeBitmap(ScreenBitmap[0]); +        if (ScreenBitmap[1]) uiDrawFreeBitmap(ScreenBitmap[1]); + +        ScreenDrawInited = true; +        ScreenBitmap[0] = uiDrawNewBitmap(params->Context, 256, 192, 0); +        ScreenBitmap[1] = uiDrawNewBitmap(params->Context, 256, 192, 0); +    } + +    int frontbuf = GPU::FrontBuffer; +    if (!ScreenBitmap[0] || !ScreenBitmap[1]) return; +    if (!GPU::Framebuffer[frontbuf][0] || !GPU::Framebuffer[frontbuf][1]) return; + +    uiRect top = {0, 0, 256, 192}; +    uiRect bot = {0, 0, 256, 192}; + +    uiDrawBitmapUpdate(ScreenBitmap[0], GPU::Framebuffer[frontbuf][0]); +    uiDrawBitmapUpdate(ScreenBitmap[1], GPU::Framebuffer[frontbuf][1]); + +    uiDrawSave(params->Context); +    uiDrawTransform(params->Context, &TopScreenTrans); +    uiDrawBitmapDraw(params->Context, ScreenBitmap[0], &top, &TopScreenRect, Config::ScreenFilter==1); +    uiDrawRestore(params->Context); + +    uiDrawSave(params->Context); +    uiDrawTransform(params->Context, &BottomScreenTrans); +    uiDrawBitmapDraw(params->Context, ScreenBitmap[1], &bot, &BottomScreenRect, Config::ScreenFilter==1); +    uiDrawRestore(params->Context); + +    OSD::Update(false, params); +} + +void OnAreaMouseEvent(uiAreaHandler* handler, uiArea* area, uiAreaMouseEvent* evt) +{ +    int x = (int)evt->X; +    int y = (int)evt->Y; + +    if (Touching && (evt->Up == 1)) +    { +        Touching = false; +        NDS::ReleaseKey(16+6); +        NDS::ReleaseScreen(); +    } +    else if (!Touching && (evt->Down == 1) && +             (x >= BottomScreenRect.X) && (y >= BottomScreenRect.Y) && +             (x < (BottomScreenRect.X+BottomScreenRect.Width)) && (y < (BottomScreenRect.Y+BottomScreenRect.Height))) +    { +        Touching = true; +        NDS::PressKey(16+6); +    } + +    if (Touching) +    { +        x -= BottomScreenRect.X; +        y -= BottomScreenRect.Y; + +        if (ScreenRotation == 0 || ScreenRotation == 2) +        { +            if (BottomScreenRect.Width != 256) +                x = (x * 256) / BottomScreenRect.Width; +            if (BottomScreenRect.Height != 192) +                y = (y * 192) / BottomScreenRect.Height; + +            if (ScreenRotation == 2) +            { +                x = 255 - x; +                y = 191 - y; +            } +        } +        else +        { +            if (BottomScreenRect.Width != 192) +                x = (x * 192) / BottomScreenRect.Width; +            if (BottomScreenRect.Height != 256) +                y = (y * 256) / BottomScreenRect.Height; + +            if (ScreenRotation == 1) +            { +                int tmp = x; +                x = y; +                y = 191 - tmp; +            } +            else +            { +                int tmp = x; +                x = 255 - y; +                y = tmp; +            } +        } + +        // clamp +        if (x < 0) x = 0; +        else if (x > 255) x = 255; +        if (y < 0) y = 0; +        else if (y > 191) y = 191; + +        // TODO: take advantage of possible extra precision when possible? (scaled window for example) +        NDS::TouchScreen(x, y); +    } +} + +void OnAreaMouseCrossed(uiAreaHandler* handler, uiArea* area, int left) +{ +} + +void OnAreaDragBroken(uiAreaHandler* handler, uiArea* area) +{ +} + +bool EventMatchesKey(uiAreaKeyEvent* evt, int val, bool checkmod) +{ +    if (val == -1) return false; + +    int key = val & 0xFFFF; +    int mod = val >> 16; +    return evt->Scancode == key && (!checkmod || evt->Modifiers == mod); +} + +int OnAreaKeyEvent(uiAreaHandler* handler, uiArea* area, uiAreaKeyEvent* evt) +{ +    // TODO: release all keys if the window loses focus? or somehow global key input? +    if (evt->Scancode == 0x38) // ALT +        return 0; +    if (evt->Modifiers == 0x2) // ALT+key +        return 0; + +    if (evt->Up) +    { +        for (int i = 0; i < 12; i++) +            if (EventMatchesKey(evt, Config::KeyMapping[i], false)) +                KeyInputMask |= (1<<i); + +        for (int i = 0; i < HK_MAX; i++) +            if (EventMatchesKey(evt, Config::HKKeyMapping[i], true)) +                KeyHotkeyMask &= ~(1<<i); +    } +    else if (!evt->Repeat) +    { +        // TODO, eventually: make savestate keys configurable? +        // F keys: 3B-44, 57-58 | SHIFT: mod. 0x4 +        if (evt->Scancode >= 0x3B && evt->Scancode <= 0x42) // F1-F8, quick savestate +        { +            if      (evt->Modifiers == 0x4) SaveState(1 + (evt->Scancode - 0x3B)); +            else if (evt->Modifiers == 0x0) LoadState(1 + (evt->Scancode - 0x3B)); +        } +        else if (evt->Scancode == 0x43) // F9, savestate from/to file +        { +            if      (evt->Modifiers == 0x4) SaveState(0); +            else if (evt->Modifiers == 0x0) LoadState(0); +        } +        else if (evt->Scancode == 0x58) // F12, undo savestate +        { +            if (evt->Modifiers == 0x0) UndoStateLoad(); +        } + +        for (int i = 0; i < 12; i++) +            if (EventMatchesKey(evt, Config::KeyMapping[i], false)) +                KeyInputMask &= ~(1<<i); + +        for (int i = 0; i < HK_MAX; i++) +            if (EventMatchesKey(evt, Config::HKKeyMapping[i], true)) +                KeyHotkeyMask |= (1<<i); + +        // REMOVE ME +        //if (evt->Scancode == 0x57) // F11 +        //    NDS::debug(0); +    } + +    return 1; +} + +void SetupScreenRects(int width, int height) +{ +    bool horizontal = false; +    bool sideways = false; + +    if (ScreenRotation == 1 || ScreenRotation == 3) +        sideways = true; + +    if (ScreenLayout == 2) horizontal = true; +    else if (ScreenLayout == 0) +    { +        if (sideways) +            horizontal = true; +    } + +    int sizemode; +    if (ScreenSizing == 3) +        sizemode = AutoScreenSizing; +    else +        sizemode = ScreenSizing; + +    int screenW, screenH, gap; +    if (sideways) +    { +        screenW = 192; +        screenH = 256; +    } +    else +    { +        screenW = 256; +        screenH = 192; +    } + +    gap = ScreenGap; + +    uiRect *topscreen, *bottomscreen; +    if (ScreenRotation == 1 || ScreenRotation == 2) +    { +        topscreen = &BottomScreenRect; +        bottomscreen = &TopScreenRect; +    } +    else +    { +        topscreen = &TopScreenRect; +        bottomscreen = &BottomScreenRect; +    } + +    if (horizontal) +    { +        // side-by-side + +        int heightreq; +        int startX = 0; + +        width -= gap; + +        if (sizemode == 0) // even +        { +            heightreq = (width * screenH) / (screenW*2); +            if (heightreq > height) +            { +                int newwidth = (height * width) / heightreq; +                startX = (width - newwidth) / 2; +                heightreq = height; +                width = newwidth; +            } +        } +        else // emph. top/bottom +        { +            heightreq = ((width - screenW) * screenH) / screenW; +            if (heightreq > height) +            { +                int newwidth = ((height * (width - screenW)) / heightreq) + screenW; +                startX = (width - newwidth) / 2; +                heightreq = height; +                width = newwidth; +            } +        } + +        if (sizemode == 2) +        { +            topscreen->Width = screenW; +            topscreen->Height = screenH; +        } +        else +        { +            topscreen->Width = (sizemode==0) ? (width / 2) : (width - screenW); +            topscreen->Height = heightreq; +        } +        topscreen->X = startX; +        topscreen->Y = ((height - heightreq) / 2) + (heightreq - topscreen->Height); + +        bottomscreen->X = topscreen->X + topscreen->Width + gap; + +        if (sizemode == 1) +        { +            bottomscreen->Width = screenW; +            bottomscreen->Height = screenH; +        } +        else +        { +            bottomscreen->Width = width - topscreen->Width; +            bottomscreen->Height = heightreq; +        } +        bottomscreen->Y = ((height - heightreq) / 2) + (heightreq - bottomscreen->Height); +    } +    else +    { +        // top then bottom + +        int widthreq; +        int startY = 0; + +        height -= gap; + +        if (sizemode == 0) // even +        { +            widthreq = (height * screenW) / (screenH*2); +            if (widthreq > width) +            { +                int newheight = (width * height) / widthreq; +                startY = (height - newheight) / 2; +                widthreq = width; +                height = newheight; +            } +        } +        else // emph. top/bottom +        { +            widthreq = ((height - screenH) * screenW) / screenH; +            if (widthreq > width) +            { +                int newheight = ((width * (height - screenH)) / widthreq) + screenH; +                startY = (height - newheight) / 2; +                widthreq = width; +                height = newheight; +            } +        } + +        if (sizemode == 2) +        { +            topscreen->Width = screenW; +            topscreen->Height = screenH; +        } +        else +        { +            topscreen->Width = widthreq; +            topscreen->Height = (sizemode==0) ? (height / 2) : (height - screenH); +        } +        topscreen->Y = startY; +        topscreen->X = (width - topscreen->Width) / 2; + +        bottomscreen->Y = topscreen->Y + topscreen->Height + gap; + +        if (sizemode == 1) +        { +            bottomscreen->Width = screenW; +            bottomscreen->Height = screenH; +        } +        else +        { +            bottomscreen->Width = widthreq; +            bottomscreen->Height = height - topscreen->Height; +        } +        bottomscreen->X = (width - bottomscreen->Width) / 2; +    } + +    // setup matrices for potential rotation + +    uiDrawMatrixSetIdentity(&TopScreenTrans); +    uiDrawMatrixSetIdentity(&BottomScreenTrans); + +    switch (ScreenRotation) +    { +    case 1: // 90° +        { +            uiDrawMatrixTranslate(&TopScreenTrans, -TopScreenRect.X, -TopScreenRect.Y); +            uiDrawMatrixRotate(&TopScreenTrans, 0, 0, M_PI/2.0f); +            uiDrawMatrixScale(&TopScreenTrans, 0, 0, +                              TopScreenRect.Width/(double)TopScreenRect.Height, +                              TopScreenRect.Height/(double)TopScreenRect.Width); +            uiDrawMatrixTranslate(&TopScreenTrans, TopScreenRect.X+TopScreenRect.Width, TopScreenRect.Y); + +            uiDrawMatrixTranslate(&BottomScreenTrans, -BottomScreenRect.X, -BottomScreenRect.Y); +            uiDrawMatrixRotate(&BottomScreenTrans, 0, 0, M_PI/2.0f); +            uiDrawMatrixScale(&BottomScreenTrans, 0, 0, +                              BottomScreenRect.Width/(double)BottomScreenRect.Height, +                              BottomScreenRect.Height/(double)BottomScreenRect.Width); +            uiDrawMatrixTranslate(&BottomScreenTrans, BottomScreenRect.X+BottomScreenRect.Width, BottomScreenRect.Y); +        } +        break; + +    case 2: // 180° +        { +            uiDrawMatrixTranslate(&TopScreenTrans, -TopScreenRect.X, -TopScreenRect.Y); +            uiDrawMatrixRotate(&TopScreenTrans, 0, 0, M_PI); +            uiDrawMatrixTranslate(&TopScreenTrans, TopScreenRect.X+TopScreenRect.Width, TopScreenRect.Y+TopScreenRect.Height); + +            uiDrawMatrixTranslate(&BottomScreenTrans, -BottomScreenRect.X, -BottomScreenRect.Y); +            uiDrawMatrixRotate(&BottomScreenTrans, 0, 0, M_PI); +            uiDrawMatrixTranslate(&BottomScreenTrans, BottomScreenRect.X+BottomScreenRect.Width, BottomScreenRect.Y+BottomScreenRect.Height); +        } +        break; + +    case 3: // 270° +        { +            uiDrawMatrixTranslate(&TopScreenTrans, -TopScreenRect.X, -TopScreenRect.Y); +            uiDrawMatrixRotate(&TopScreenTrans, 0, 0, -M_PI/2.0f); +            uiDrawMatrixScale(&TopScreenTrans, 0, 0, +                              TopScreenRect.Width/(double)TopScreenRect.Height, +                              TopScreenRect.Height/(double)TopScreenRect.Width); +            uiDrawMatrixTranslate(&TopScreenTrans, TopScreenRect.X, TopScreenRect.Y+TopScreenRect.Height); + +            uiDrawMatrixTranslate(&BottomScreenTrans, -BottomScreenRect.X, -BottomScreenRect.Y); +            uiDrawMatrixRotate(&BottomScreenTrans, 0, 0, -M_PI/2.0f); +            uiDrawMatrixScale(&BottomScreenTrans, 0, 0, +                              BottomScreenRect.Width/(double)BottomScreenRect.Height, +                              BottomScreenRect.Height/(double)BottomScreenRect.Width); +            uiDrawMatrixTranslate(&BottomScreenTrans, BottomScreenRect.X, BottomScreenRect.Y+BottomScreenRect.Height); +        } +        break; +    } + +    GL_ScreenSizeDirty = true; +} + +void SetMinSize(int w, int h) +{ +    int cw, ch; +    uiWindowContentSize(MainWindow, &cw, &ch); + +    uiControlSetMinSize(uiControl(MainDrawArea), w, h); +    if ((cw < w) || (ch < h)) +    { +        if (cw < w) cw = w; +        if (ch < h) ch = h; +        uiWindowSetContentSize(MainWindow, cw, ch); +    } +} + +void OnAreaResize(uiAreaHandler* handler, uiArea* area, int width, int height) +{ +    SetupScreenRects(width, height); + +    // TODO: +    // should those be the size of the uiArea, or the size of the window client area? +    // for now the uiArea fills the whole window anyway +    // but... we never know, I guess +    WindowWidth = width; +    WindowHeight = height; + +    int ismax = uiWindowMaximized(MainWindow); +    int ismin = uiWindowMinimized(MainWindow); + +    Config::WindowMaximized = ismax; +    if (!ismax && !ismin) +    { +        Config::WindowWidth = width; +        Config::WindowHeight = height; +    } + +    OSD::WindowResized(Screen_UseGL); +} + + +void Run() +{ +    EmuRunning = 1; +    RunningSomething = true; + +    SPU::InitOutput(); +    AudioSampleFrac = 0; +    SDL_PauseAudioDevice(AudioDevice, 0); +    SDL_PauseAudioDevice(MicDevice, 0); + +    uiMenuItemEnable(MenuItem_SaveState); +    uiMenuItemEnable(MenuItem_LoadState); + +    if (SavestateLoaded) +        uiMenuItemEnable(MenuItem_UndoStateLoad); +    else +        uiMenuItemDisable(MenuItem_UndoStateLoad); + +    for (int i = 0; i < 8; i++) +    { +        char ssfile[1024]; +        GetSavestateName(i+1, ssfile, 1024); +        if (Platform::FileExists(ssfile)) uiMenuItemEnable(MenuItem_LoadStateSlot[i]); +        else                              uiMenuItemDisable(MenuItem_LoadStateSlot[i]); +    } + +    for (int i = 0; i < 9; i++) uiMenuItemEnable(MenuItem_SaveStateSlot[i]); +    uiMenuItemEnable(MenuItem_LoadStateSlot[8]); + +    uiMenuItemEnable(MenuItem_Pause); +    uiMenuItemEnable(MenuItem_Reset); +    uiMenuItemEnable(MenuItem_Stop); +    uiMenuItemSetChecked(MenuItem_Pause, 0); +} + +void TogglePause(void* blarg) +{ +    if (!RunningSomething) return; + +    if (EmuRunning == 1) +    { +        // enable pause +        EmuRunning = 2; +        uiMenuItemSetChecked(MenuItem_Pause, 1); + +        SPU::DrainOutput(); +        SDL_PauseAudioDevice(AudioDevice, 1); +        SDL_PauseAudioDevice(MicDevice, 1); + +        OSD::AddMessage(0, "Paused"); +    } +    else +    { +        // disable pause +        EmuRunning = 1; +        uiMenuItemSetChecked(MenuItem_Pause, 0); + +        SPU::InitOutput(); +        AudioSampleFrac = 0; +        SDL_PauseAudioDevice(AudioDevice, 0); +        SDL_PauseAudioDevice(MicDevice, 0); + +        OSD::AddMessage(0, "Resumed"); +    } +} + +void Reset(void* blarg) +{ +    if (!RunningSomething) return; + +    EmuRunning = 2; +    while (EmuStatus != 2); + +    SavestateLoaded = false; +    uiMenuItemDisable(MenuItem_UndoStateLoad); + +    if (ROMPath[0][0] == '\0') +        NDS::LoadBIOS(); +    else +    { +        SetupSRAMPath(0); +        NDS::LoadROM(ROMPath[0], SRAMPath[0], Config::DirectBoot); +    } + +    if (ROMPath[1][0] != '\0') +    { +        SetupSRAMPath(1); +        NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]); +    } + +    Run(); + +    OSD::AddMessage(0, "Reset"); +} + +void Stop(bool internal) +{ +    EmuRunning = 2; +    if (!internal) // if shutting down from the UI thread, wait till the emu thread has stopped +        while (EmuStatus != 2); +    RunningSomething = false; + +    // eject any inserted GBA cartridge +    GBACart::Eject(); +    ROMPath[1][0] = '\0'; + +    uiWindowSetTitle(MainWindow, "melonDS " MELONDS_VERSION); + +    for (int i = 0; i < 9; i++) uiMenuItemDisable(MenuItem_SaveStateSlot[i]); +    for (int i = 0; i < 9; i++) uiMenuItemDisable(MenuItem_LoadStateSlot[i]); +    uiMenuItemDisable(MenuItem_UndoStateLoad); + +    uiMenuItemDisable(MenuItem_Pause); +    uiMenuItemDisable(MenuItem_Reset); +    uiMenuItemDisable(MenuItem_Stop); +    uiMenuItemSetChecked(MenuItem_Pause, 0); + +    uiAreaQueueRedrawAll(MainDrawArea); + +    SPU::DrainOutput(); +    SDL_PauseAudioDevice(AudioDevice, 1); +    SDL_PauseAudioDevice(MicDevice, 1); + +    OSD::AddMessage(0xFFC040, "Shutdown"); +} + +void SetupSRAMPath(int slot) +{ +    strncpy(SRAMPath[slot], ROMPath[slot], 1023); +    SRAMPath[slot][1023] = '\0'; +    strncpy(SRAMPath[slot] + strlen(ROMPath[slot]) - 3, "sav", 3); +} + +void TryLoadROM(char* file, int slot, int prevstatus) +{ +    char oldpath[1024]; +    char oldsram[1024]; +    strncpy(oldpath, ROMPath[slot], 1024); +    strncpy(oldsram, SRAMPath[slot], 1024); + +    strncpy(ROMPath[slot], file, 1023); +    ROMPath[slot][1023] = '\0'; + +    SetupSRAMPath(0); +    SetupSRAMPath(1); + +    if (slot == 0 && NDS::LoadROM(ROMPath[slot], SRAMPath[slot], Config::DirectBoot)) +    { +        SavestateLoaded = false; +        uiMenuItemDisable(MenuItem_UndoStateLoad); + +        // Reload the inserted GBA cartridge (if any) +        if (ROMPath[1][0] != '\0') NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]); + +        strncpy(PrevSRAMPath[slot], SRAMPath[slot], 1024); // safety +        Run(); +    } +    else if (slot == 1 && NDS::LoadGBAROM(ROMPath[slot], SRAMPath[slot])) +    { +        SavestateLoaded = false; +        uiMenuItemDisable(MenuItem_UndoStateLoad); + +        strncpy(PrevSRAMPath[slot], SRAMPath[slot], 1024); // safety +        if (RunningSomething) Run(); // do not start just from a GBA cart +    } +    else +    { +        uiMsgBoxError(MainWindow, +                      "Failed to load the ROM", +                      "Make sure the file can be accessed and isn't opened in another application."); + +        strncpy(ROMPath[slot], oldpath, 1024); +        strncpy(SRAMPath[slot], oldsram, 1024); +        EmuRunning = prevstatus; +    } +} + + +// SAVESTATE TODO +// * configurable paths. not everyone wants their ROM directory to be polluted, I guess. + +void GetSavestateName(int slot, char* filename, int len) +{ +    int pos; + +    if (ROMPath[0][0] == '\0') // running firmware, no ROM +    { +        strcpy(filename, "firmware"); +        pos = 8; +    } +    else +    { +        int l = strlen(ROMPath[0]); +        pos = l; +        while (ROMPath[0][pos] != '.' && pos > 0) pos--; +        if (pos == 0) pos = l; + +        // avoid buffer overflow. shoddy +        if (pos > len-5) pos = len-5; + +        strncpy(&filename[0], ROMPath[0], pos); +    } +    strcpy(&filename[pos], ".ml"); +    filename[pos+3] = '0'+slot; +    filename[pos+4] = '\0'; +} + +void LoadState(int slot) +{ +    int prevstatus = EmuRunning; +    EmuRunning = 2; +    while (EmuStatus != 2); + +    char filename[1024]; + +    if (slot > 0) +    { +        GetSavestateName(slot, filename, 1024); +    } +    else +    { +        char* file = uiOpenFile(MainWindow, "melonDS savestate (any)|*.ml1;*.ml2;*.ml3;*.ml4;*.ml5;*.ml6;*.ml7;*.ml8;*.mln", Config::LastROMFolder); +        if (!file) +        { +            EmuRunning = prevstatus; +            return; +        } + +        strncpy(filename, file, 1023); +        filename[1023] = '\0'; +        uiFreeText(file); +    } + +    if (!Platform::FileExists(filename)) +    { +        char msg[64]; +        if (slot > 0) sprintf(msg, "State slot %d is empty", slot); +        else          sprintf(msg, "State file does not exist"); +        OSD::AddMessage(0xFFA0A0, msg); + +        EmuRunning = prevstatus; +        return; +    } + +    u32 oldGBACartCRC = GBACart::CartCRC; + +    // backup +    Savestate* backup = new Savestate("timewarp.mln", true); +    NDS::DoSavestate(backup); +    delete backup; + +    bool failed = false; + +    Savestate* state = new Savestate(filename, false); +    if (state->Error) +    { +        delete state; + +        uiMsgBoxError(MainWindow, "Error", "Could not load savestate file."); + +        // current state might be crapoed, so restore from sane backup +        state = new Savestate("timewarp.mln", false); +        failed = true; +    } + +    NDS::DoSavestate(state); +    delete state; + +    if (!failed) +    { +        if (Config::SavestateRelocSRAM && ROMPath[0][0]!='\0') +        { +            strncpy(PrevSRAMPath[0], SRAMPath[0], 1024); + +            strncpy(SRAMPath[0], filename, 1019); +            int len = strlen(SRAMPath[0]); +            strcpy(&SRAMPath[0][len], ".sav"); +            SRAMPath[0][len+4] = '\0'; + +            NDS::RelocateSave(SRAMPath[0], false); +        } + +        bool loadedPartialGBAROM = false; + +        // in case we have a GBA cart inserted, and the GBA ROM changes +        // due to having loaded a save state, we do not want to reload +        // the previous cartridge on reset, or commit writes to any +        // loaded save file. therefore, their paths are "nulled". +        if (GBACart::CartInserted && GBACart::CartCRC != oldGBACartCRC) +        { +            ROMPath[1][0] = '\0'; +            SRAMPath[1][0] = '\0'; +            loadedPartialGBAROM = true; +        } + +        char msg[64]; +        if (slot > 0) sprintf(msg, "State loaded from slot %d%s", +                        slot, loadedPartialGBAROM ? " (GBA ROM header only)" : ""); +        else          sprintf(msg, "State loaded from file%s", +                        loadedPartialGBAROM ? " (GBA ROM header only)" : ""); +        OSD::AddMessage(0, msg); + +        SavestateLoaded = true; +        uiMenuItemEnable(MenuItem_UndoStateLoad); +    } + +    EmuRunning = prevstatus; +} + +void SaveState(int slot) +{ +    int prevstatus = EmuRunning; +    EmuRunning = 2; +    while (EmuStatus != 2); + +    char filename[1024]; + +    if (slot > 0) +    { +        GetSavestateName(slot, filename, 1024); +    } +    else +    { +        char* file = uiSaveFile(MainWindow, "melonDS savestate (*.mln)|*.mln", Config::LastROMFolder); +        if (!file) +        { +            EmuRunning = prevstatus; +            return; +        } + +        strncpy(filename, file, 1023); +        filename[1023] = '\0'; +        uiFreeText(file); +    } + +    Savestate* state = new Savestate(filename, true); +    if (state->Error) +    { +        delete state; + +        uiMsgBoxError(MainWindow, "Error", "Could not save state."); +    } +    else +    { +        NDS::DoSavestate(state); +        delete state; + +        if (slot > 0) +            uiMenuItemEnable(MenuItem_LoadStateSlot[slot-1]); + +        if (Config::SavestateRelocSRAM && ROMPath[0][0]!='\0') +        { +            strncpy(SRAMPath[0], filename, 1019); +            int len = strlen(SRAMPath[0]); +            strcpy(&SRAMPath[0][len], ".sav"); +            SRAMPath[0][len+4] = '\0'; + +            NDS::RelocateSave(SRAMPath[0], true); +        } +    } + +    char msg[64]; +    if (slot > 0) sprintf(msg, "State saved to slot %d", slot); +    else          sprintf(msg, "State saved to file"); +    OSD::AddMessage(0, msg); + +    EmuRunning = prevstatus; +} + +void UndoStateLoad() +{ +    if (!SavestateLoaded) return; + +    int prevstatus = EmuRunning; +    EmuRunning = 2; +    while (EmuStatus != 2); + +    // pray that this works +    // what do we do if it doesn't??? +    // but it should work. +    Savestate* backup = new Savestate("timewarp.mln", false); +    NDS::DoSavestate(backup); +    delete backup; + +    if (ROMPath[0][0]!='\0') +    { +        strncpy(SRAMPath[0], PrevSRAMPath[0], 1024); +        NDS::RelocateSave(SRAMPath[0], false); +    } + +    OSD::AddMessage(0, "State load undone"); + +    EmuRunning = prevstatus; +} + + +void CloseAllDialogs() +{ +    DlgAudioSettings::Close(); +    DlgEmuSettings::Close(); +    DlgInputConfig::Close(0); +    DlgInputConfig::Close(1); +    DlgVideoSettings::Close(); +    DlgWifiSettings::Close(); +} + + +int OnCloseWindow(uiWindow* window, void* blarg) +{ +    EmuRunning = 3; +    while (EmuStatus != 3); + +    CloseAllDialogs(); +    StopEmuThread(); +    uiQuit(); +    return 1; +} + +void OnDropFile(uiWindow* window, char* file, void* blarg) +{ +    char* ext = &file[strlen(file)-3]; +    int prevstatus = EmuRunning; + +    if (!strcasecmp(ext, "nds") || !strcasecmp(ext, "srl")) +    { +        if (RunningSomething) +        { +            EmuRunning = 2; +            while (EmuStatus != 2); +        } + +        TryLoadROM(file, 0, prevstatus); +    } +    else if (!strcasecmp(ext, "gba")) +    { +        TryLoadROM(file, 1, prevstatus); +    } +} + +void OnGetFocus(uiWindow* window, void* blarg) +{ +    uiControlSetFocus(uiControl(MainDrawArea)); +} + +void OnLoseFocus(uiWindow* window, void* blarg) +{ +    // TODO: shit here? +} + +void OnCloseByMenu(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    EmuRunning = 3; +    while (EmuStatus != 3); + +    CloseAllDialogs(); +    StopEmuThread(); +    DestroyMainWindow(); +    uiQuit(); +} + +void OnOpenFile(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    int prevstatus = EmuRunning; +    EmuRunning = 2; +    while (EmuStatus != 2); + +    char* file = uiOpenFile(window, "DS ROM (*.nds)|*.nds;*.srl|GBA ROM (*.gba)|*.gba|Any file|*.*", Config::LastROMFolder); +    if (!file) +    { +        EmuRunning = prevstatus; +        return; +    } + +    int pos = strlen(file)-1; +    while (file[pos] != '/' && file[pos] != '\\' && pos > 0) pos--; +    strncpy(Config::LastROMFolder, file, pos); +    Config::LastROMFolder[pos] = '\0'; +    char* ext = &file[strlen(file)-3]; + +    if (!strcasecmp(ext, "gba")) +    { +        TryLoadROM(file, 1, prevstatus); +    } +    else +    { +        TryLoadROM(file, 0, prevstatus); +    } + +    uiFreeText(file); +} + +void OnSaveState(uiMenuItem* item, uiWindow* window, void* param) +{ +    int slot = *(int*)param; +    SaveState(slot); +} + +void OnLoadState(uiMenuItem* item, uiWindow* window, void* param) +{ +    int slot = *(int*)param; +    LoadState(slot); +} + +void OnUndoStateLoad(uiMenuItem* item, uiWindow* window, void* param) +{ +    UndoStateLoad(); +} + +void OnRun(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    if (!RunningSomething) +    { +        ROMPath[0][0] = '\0'; +        NDS::LoadBIOS(); + +        if (ROMPath[1][0] != '\0') +        { +            SetupSRAMPath(1); +            NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]); +        } +    } + +    Run(); +} + +void OnPause(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    TogglePause(NULL); +} + +void OnReset(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    Reset(NULL); +} + +void OnStop(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    if (!RunningSomething) return; + +    Stop(false); +} + +void OnOpenEmuSettings(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    DlgEmuSettings::Open(); +} + +void OnOpenInputConfig(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    DlgInputConfig::Open(0); +} + +void OnOpenHotkeyConfig(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    DlgInputConfig::Open(1); +} + +void OnOpenVideoSettings(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    DlgVideoSettings::Open(); +} + +void OnOpenAudioSettings(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    DlgAudioSettings::Open(); +} + +void OnOpenWifiSettings(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    DlgWifiSettings::Open(); +} + + +void OnSetSavestateSRAMReloc(uiMenuItem* item, uiWindow* window, void* param) +{ +    Config::SavestateRelocSRAM = uiMenuItemChecked(item) ? 1:0; +} + + +void EnsureProperMinSize() +{ +    bool isHori = (ScreenRotation == 1 || ScreenRotation == 3); + +    int w0 = 256; +    int h0 = 192; +    int w1 = 256; +    int h1 = 192; + +    if (ScreenLayout == 0) // natural +    { +        if (isHori) +            SetMinSize(h0+ScreenGap+h1, std::max(w0,w1)); +        else +            SetMinSize(std::max(w0,w1), h0+ScreenGap+h1); +    } +    else if (ScreenLayout == 1) // vertical +    { +        if (isHori) +            SetMinSize(std::max(h0,h1), w0+ScreenGap+w1); +        else +            SetMinSize(std::max(w0,w1), h0+ScreenGap+h1); +    } +    else // horizontal +    { +        if (isHori) +            SetMinSize(h0+ScreenGap+h1, std::max(w0,w1)); +        else +            SetMinSize(w0+ScreenGap+w1, std::max(h0,h1)); +    } +} + +void OnSetScreenSize(uiMenuItem* item, uiWindow* window, void* param) +{ +    int factor = *(int*)param; +    bool isHori = (ScreenRotation == 1 || ScreenRotation == 3); + +    int w = 256*factor; +    int h = 192*factor; + +    // FIXME + +    if (ScreenLayout == 0) // natural +    { +        if (isHori) +            uiWindowSetContentSize(window, (h*2)+ScreenGap, w); +        else +            uiWindowSetContentSize(window, w, (h*2)+ScreenGap); +    } +    else if (ScreenLayout == 1) // vertical +    { +        if (isHori) +            uiWindowSetContentSize(window, h, (w*2)+ScreenGap); +        else +            uiWindowSetContentSize(window, w, (h*2)+ScreenGap); +    } +    else // horizontal +    { +        if (isHori) +            uiWindowSetContentSize(window, (h*2)+ScreenGap, w); +        else +            uiWindowSetContentSize(window, (w*2)+ScreenGap, h); +    } +} + +void OnSetScreenRotation(uiMenuItem* item, uiWindow* window, void* param) +{ +    int rot = *(int*)param; + +    int oldrot = ScreenRotation; +    ScreenRotation = rot; + +    int w, h; +    uiWindowContentSize(window, &w, &h); + +    bool isHori = (rot == 1 || rot == 3); +    bool wasHori = (oldrot == 1 || oldrot == 3); + +    EnsureProperMinSize(); + +    if (ScreenLayout == 0) // natural +    { +        if (isHori ^ wasHori) +        { +            int blarg = h; +            h = w; +            w = blarg; + +            uiWindowSetContentSize(window, w, h); +        } +    } + +    SetupScreenRects(w, h); + +    for (int i = 0; i < 4; i++) +        uiMenuItemSetChecked(MenuItem_ScreenRot[i], i==ScreenRotation); +} + +void OnSetScreenGap(uiMenuItem* item, uiWindow* window, void* param) +{ +    int gap = *(int*)param; + +    //int oldgap = ScreenGap; +    ScreenGap = gap; + +    EnsureProperMinSize(); +    SetupScreenRects(WindowWidth, WindowHeight); + +    for (int i = 0; i < 6; i++) +        uiMenuItemSetChecked(MenuItem_ScreenGap[i], kScreenGap[i]==ScreenGap); +} + +void OnSetScreenLayout(uiMenuItem* item, uiWindow* window, void* param) +{ +    int layout = *(int*)param; +    ScreenLayout = layout; + +    EnsureProperMinSize(); +    SetupScreenRects(WindowWidth, WindowHeight); + +    for (int i = 0; i < 3; i++) +        uiMenuItemSetChecked(MenuItem_ScreenLayout[i], i==ScreenLayout); +} + +void OnSetScreenSizing(uiMenuItem* item, uiWindow* window, void* param) +{ +    int sizing = *(int*)param; +    ScreenSizing = sizing; + +    SetupScreenRects(WindowWidth, WindowHeight); + +    for (int i = 0; i < 4; i++) +        uiMenuItemSetChecked(MenuItem_ScreenSizing[i], i==ScreenSizing); +} + +void OnSetScreenFiltering(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    int chk = uiMenuItemChecked(item); +    if (chk != 0) Config::ScreenFilter = 1; +    else          Config::ScreenFilter = 0; +} + +void OnSetLimitFPS(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    int chk = uiMenuItemChecked(item); +    if (chk != 0) Config::LimitFPS = true; +    else          Config::LimitFPS = false; +} + +void OnSetAudioSync(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    int chk = uiMenuItemChecked(item); +    if (chk != 0) Config::AudioSync = true; +    else          Config::AudioSync = false; +} + +void OnSetShowOSD(uiMenuItem* item, uiWindow* window, void* blarg) +{ +    int chk = uiMenuItemChecked(item); +    if (chk != 0) Config::ShowOSD = true; +    else          Config::ShowOSD = false; +} + +void ApplyNewSettings(int type) +{ +#ifdef JIT_ENABLED +    if (type == 4) +    { +        Reset(NULL); +        return; +    } +#endif + +    if (!RunningSomething) +    { +        if (type == 1) return; +    } + +    int prevstatus = EmuRunning; +    EmuRunning = 3; +    while (EmuStatus != 3); + +    if (type == 0) // 3D renderer settings +    { +        if (Screen_UseGL) uiGLMakeContextCurrent(GLContext); +        GPU3D::UpdateRendererConfig(); +        if (Screen_UseGL) uiGLMakeContextCurrent(NULL); + +        GL_3DScale = Config::GL_ScaleFactor; // dorp +        GL_ScreenSizeDirty = true; +    } +    else if (type == 1) // wifi settings +    { +        if (Wifi::MPInited) +        { +            Platform::MP_DeInit(); +            Platform::MP_Init(); +        } + +        Platform::LAN_DeInit(); +        Platform::LAN_Init(); +    } +    else if (type == 2) // video output method +    { +        bool usegl = Config::ScreenUseGL || (Config::_3DRenderer != 0); +        if (usegl != Screen_UseGL) +        { +            if (Screen_UseGL) uiGLMakeContextCurrent(GLContext); +            GPU3D::DeInitRenderer(); +            OSD::DeInit(Screen_UseGL); +            if (Screen_UseGL) uiGLMakeContextCurrent(NULL); + +            Screen_UseGL = usegl; +            RecreateMainWindow(usegl); + +            if (Screen_UseGL) uiGLMakeContextCurrent(GLContext); +            GPU3D::InitRenderer(Screen_UseGL); +            if (Screen_UseGL) uiGLMakeContextCurrent(NULL); +        } +    } +    else if (type == 3) // 3D renderer +    { +        if (Screen_UseGL) uiGLMakeContextCurrent(GLContext); +        GPU3D::DeInitRenderer(); +        GPU3D::InitRenderer(Screen_UseGL); +        if (Screen_UseGL) uiGLMakeContextCurrent(NULL); +    } +    EmuRunning = prevstatus; +} + + +void CreateMainWindowMenu() +{ +    uiMenu* menu; +    uiMenuItem* menuitem; + +    menu = uiNewMenu("File"); +    menuitem = uiMenuAppendItem(menu, "Open ROM..."); +    uiMenuItemOnClicked(menuitem, OnOpenFile, NULL); +    uiMenuAppendSeparator(menu); +    { +        uiMenu* submenu = uiNewMenu("Save state"); + +        for (int i = 0; i < 9; i++) +        { +            char name[32]; +            if (i < 8) +                sprintf(name, "%d\tShift+F%d", kSavestateNum[i], kSavestateNum[i]); +            else +                strcpy(name, "File...\tShift+F9"); + +            uiMenuItem* ssitem = uiMenuAppendItem(submenu, name); +            uiMenuItemOnClicked(ssitem, OnSaveState, (void*)&kSavestateNum[i]); + +            MenuItem_SaveStateSlot[i] = ssitem; +        } + +        MenuItem_SaveState = uiMenuAppendSubmenu(menu, submenu); +    } +    { +        uiMenu* submenu = uiNewMenu("Load state"); + +        for (int i = 0; i < 9; i++) +        { +            char name[32]; +            if (i < 8) +                sprintf(name, "%d\tF%d", kSavestateNum[i], kSavestateNum[i]); +            else +                strcpy(name, "File...\tF9"); + +            uiMenuItem* ssitem = uiMenuAppendItem(submenu, name); +            uiMenuItemOnClicked(ssitem, OnLoadState, (void*)&kSavestateNum[i]); + +            MenuItem_LoadStateSlot[i] = ssitem; +        } + +        MenuItem_LoadState = uiMenuAppendSubmenu(menu, submenu); +    } +    menuitem = uiMenuAppendItem(menu, "Undo state load\tF12"); +    uiMenuItemOnClicked(menuitem, OnUndoStateLoad, NULL); +    MenuItem_UndoStateLoad = menuitem; +    uiMenuAppendSeparator(menu); +    menuitem = uiMenuAppendItem(menu, "Quit"); +    uiMenuItemOnClicked(menuitem, OnCloseByMenu, NULL); + +    menu = uiNewMenu("System"); +    menuitem = uiMenuAppendItem(menu, "Run"); +    uiMenuItemOnClicked(menuitem, OnRun, NULL); +    menuitem = uiMenuAppendCheckItem(menu, "Pause"); +    uiMenuItemOnClicked(menuitem, OnPause, NULL); +    MenuItem_Pause = menuitem; +    uiMenuAppendSeparator(menu); +    menuitem = uiMenuAppendItem(menu, "Reset"); +    uiMenuItemOnClicked(menuitem, OnReset, NULL); +    MenuItem_Reset = menuitem; +    menuitem = uiMenuAppendItem(menu, "Stop"); +    uiMenuItemOnClicked(menuitem, OnStop, NULL); +    MenuItem_Stop = menuitem; + +    menu = uiNewMenu("Config"); +    { +        menuitem = uiMenuAppendItem(menu, "Emu settings"); +        uiMenuItemOnClicked(menuitem, OnOpenEmuSettings, NULL); +        menuitem = uiMenuAppendItem(menu, "Input config"); +        uiMenuItemOnClicked(menuitem, OnOpenInputConfig, NULL); +        menuitem = uiMenuAppendItem(menu, "Hotkey config"); +        uiMenuItemOnClicked(menuitem, OnOpenHotkeyConfig, NULL); +        menuitem = uiMenuAppendItem(menu, "Video settings"); +        uiMenuItemOnClicked(menuitem, OnOpenVideoSettings, NULL); +        menuitem = uiMenuAppendItem(menu, "Audio settings"); +        uiMenuItemOnClicked(menuitem, OnOpenAudioSettings, NULL); +        menuitem = uiMenuAppendItem(menu, "Wifi settings"); +        uiMenuItemOnClicked(menuitem, OnOpenWifiSettings, NULL); +    } +    uiMenuAppendSeparator(menu); +    { +        uiMenu* submenu = uiNewMenu("Savestate settings"); + +        MenuItem_SavestateSRAMReloc = uiMenuAppendCheckItem(submenu, "Separate savefiles"); +        uiMenuItemOnClicked(MenuItem_SavestateSRAMReloc, OnSetSavestateSRAMReloc, NULL); + +        uiMenuAppendSubmenu(menu, submenu); +    } +    uiMenuAppendSeparator(menu); +    { +        uiMenu* submenu = uiNewMenu("Screen size"); + +        for (int i = 0; i < 4; i++) +        { +            char name[32]; +            sprintf(name, "%dx", kScreenSize[i]); +            uiMenuItem* item = uiMenuAppendItem(submenu, name); +            uiMenuItemOnClicked(item, OnSetScreenSize, (void*)&kScreenSize[i]); +        } + +        uiMenuAppendSubmenu(menu, submenu); +    } +    { +        uiMenu* submenu = uiNewMenu("Screen rotation"); + +        for (int i = 0; i < 4; i++) +        { +            char name[32]; +            sprintf(name, "%d", kScreenRot[i]*90); +            MenuItem_ScreenRot[i] = uiMenuAppendCheckItem(submenu, name); +            uiMenuItemOnClicked(MenuItem_ScreenRot[i], OnSetScreenRotation, (void*)&kScreenRot[i]); +        } + +        uiMenuAppendSubmenu(menu, submenu); +    } +    { +        uiMenu* submenu = uiNewMenu("Mid-screen gap"); + +        //for (int i = 0; kScreenGap[i] != -1; i++) +        for (int i = 0; i < 6; i++) +        { +            char name[32]; +            sprintf(name, "%d pixels", kScreenGap[i]); +            MenuItem_ScreenGap[i] = uiMenuAppendCheckItem(submenu, name); +            uiMenuItemOnClicked(MenuItem_ScreenGap[i], OnSetScreenGap, (void*)&kScreenGap[i]); +        } + +        uiMenuAppendSubmenu(menu, submenu); +    } +    { +        uiMenu* submenu = uiNewMenu("Screen layout"); + +        MenuItem_ScreenLayout[0] = uiMenuAppendCheckItem(submenu, "Natural"); +        uiMenuItemOnClicked(MenuItem_ScreenLayout[0], OnSetScreenLayout, (void*)&kScreenLayout[0]); +        MenuItem_ScreenLayout[1] = uiMenuAppendCheckItem(submenu, "Vertical"); +        uiMenuItemOnClicked(MenuItem_ScreenLayout[1], OnSetScreenLayout, (void*)&kScreenLayout[1]); +        MenuItem_ScreenLayout[2] = uiMenuAppendCheckItem(submenu, "Horizontal"); +        uiMenuItemOnClicked(MenuItem_ScreenLayout[2], OnSetScreenLayout, (void*)&kScreenLayout[2]); + +        uiMenuAppendSubmenu(menu, submenu); +    } +    { +        uiMenu* submenu = uiNewMenu("Screen sizing"); + +        MenuItem_ScreenSizing[0] = uiMenuAppendCheckItem(submenu, "Even"); +        uiMenuItemOnClicked(MenuItem_ScreenSizing[0], OnSetScreenSizing, (void*)&kScreenSizing[0]); +        MenuItem_ScreenSizing[1] = uiMenuAppendCheckItem(submenu, "Emphasize top"); +        uiMenuItemOnClicked(MenuItem_ScreenSizing[1], OnSetScreenSizing, (void*)&kScreenSizing[1]); +        MenuItem_ScreenSizing[2] = uiMenuAppendCheckItem(submenu, "Emphasize bottom"); +        uiMenuItemOnClicked(MenuItem_ScreenSizing[2], OnSetScreenSizing, (void*)&kScreenSizing[2]); +        MenuItem_ScreenSizing[3] = uiMenuAppendCheckItem(submenu, "Auto"); +        uiMenuItemOnClicked(MenuItem_ScreenSizing[3], OnSetScreenSizing, (void*)&kScreenSizing[3]); + +        uiMenuAppendSubmenu(menu, submenu); +    } + +    MenuItem_ScreenFilter = uiMenuAppendCheckItem(menu, "Screen filtering"); +    uiMenuItemOnClicked(MenuItem_ScreenFilter, OnSetScreenFiltering, NULL); + +    MenuItem_ShowOSD = uiMenuAppendCheckItem(menu, "Show OSD"); +    uiMenuItemOnClicked(MenuItem_ShowOSD, OnSetShowOSD, NULL); + +    uiMenuAppendSeparator(menu); + +    MenuItem_LimitFPS = uiMenuAppendCheckItem(menu, "Limit framerate"); +    uiMenuItemOnClicked(MenuItem_LimitFPS, OnSetLimitFPS, NULL); + +    MenuItem_AudioSync = uiMenuAppendCheckItem(menu, "Audio sync"); +    uiMenuItemOnClicked(MenuItem_AudioSync, OnSetAudioSync, NULL); +} + +void CreateMainWindow(bool opengl) +{ +    MainWindow = uiNewWindow("melonDS " MELONDS_VERSION, +                             WindowWidth, WindowHeight, +                             Config::WindowMaximized, 1, 1); +    uiWindowOnClosing(MainWindow, OnCloseWindow, NULL); + +    uiWindowSetDropTarget(MainWindow, 1); +    uiWindowOnDropFile(MainWindow, OnDropFile, NULL); + +    uiWindowOnGetFocus(MainWindow, OnGetFocus, NULL); +    uiWindowOnLoseFocus(MainWindow, OnLoseFocus, NULL); + +    ScreenDrawInited = false; +    bool opengl_good = opengl; + +    if (!opengl) MainDrawArea = uiNewArea(&MainDrawAreaHandler); +    else         MainDrawArea = uiNewGLArea(&MainDrawAreaHandler, kGLVersions); + +    uiWindowSetChild(MainWindow, uiControl(MainDrawArea)); +    uiControlSetMinSize(uiControl(MainDrawArea), 256, 384); +    uiAreaSetBackgroundColor(MainDrawArea, 0, 0, 0); + +    uiControlShow(uiControl(MainWindow)); +    uiControlSetFocus(uiControl(MainDrawArea)); + +    if (opengl_good) +    { +        GLContext = uiAreaGetGLContext(MainDrawArea); +        if (!GLContext) opengl_good = false; +    } +    if (opengl_good) +    { +        uiGLMakeContextCurrent(GLContext); +        uiGLSetVSync(Config::ScreenVSync); +        if (!GLScreen_Init()) opengl_good = false; +        if (opengl_good) +        { +            OpenGL_UseShaderProgram(GL_ScreenShaderOSD); +            OSD::Init(true); +        } +        uiGLMakeContextCurrent(NULL); +    } + +    if (opengl && !opengl_good) +    { +        printf("OpenGL: initialization failed\n"); +        RecreateMainWindow(false); +        Screen_UseGL = false; +    } + +    if (!opengl) OSD::Init(false); +} + +void DestroyMainWindow() +{ +    uiControlDestroy(uiControl(MainWindow)); + +    if (ScreenBitmap[0]) uiDrawFreeBitmap(ScreenBitmap[0]); +    if (ScreenBitmap[1]) uiDrawFreeBitmap(ScreenBitmap[1]); + +    ScreenBitmap[0] = NULL; +    ScreenBitmap[1] = NULL; +} + +void RecreateMainWindow(bool opengl) +{ +    int winX, winY, maxi; +    uiWindowPosition(MainWindow, &winX, &winY); +    maxi = uiWindowMaximized(MainWindow); +    DestroyMainWindow(); +    CreateMainWindow(opengl); +    uiWindowSetPosition(MainWindow, winX, winY); +    uiWindowSetMaximized(MainWindow, maxi); +} + + +int main(int argc, char** argv) +{ +    srand(time(NULL)); + +    printf("melonDS " MELONDS_VERSION "\n"); +    printf(MELONDS_URL "\n"); + +#if defined(__WIN32__) || defined(UNIX_PORTABLE) +    if (argc > 0 && strlen(argv[0]) > 0) +    { +        int len = strlen(argv[0]); +        while (len > 0) +        { +            if (argv[0][len] == '/') break; +            if (argv[0][len] == '\\') break; +            len--; +        } +        if (len > 0) +        { +            EmuDirectory = new char[len+1]; +            strncpy(EmuDirectory, argv[0], len); +            EmuDirectory[len] = '\0'; +        } +        else +        { +            EmuDirectory = new char[2]; +            strcpy(EmuDirectory, "."); +        } +    } +    else +    { +        EmuDirectory = new char[2]; +        strcpy(EmuDirectory, "."); +    } +#else +	const char* confdir = g_get_user_config_dir(); +	const char* confname = "/melonDS"; +	EmuDirectory = new char[strlen(confdir) + strlen(confname) + 1]; +	strcat(EmuDirectory, confdir); +	strcat(EmuDirectory, confname); +#endif + +    // http://stackoverflow.com/questions/14543333/joystick-wont-work-using-sdl +    SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + +    if (SDL_Init(SDL_INIT_HAPTIC) < 0) +    { +        printf("SDL couldn't init rumble\n"); +    } +    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0) +    { +        printf("SDL shat itself :(\n"); +        return 1; +    } + +    SDL_JoystickEventState(SDL_ENABLE); + +    uiInitOptions ui_opt; +    memset(&ui_opt, 0, sizeof(uiInitOptions)); +    const char* ui_err = uiInit(&ui_opt); +    if (ui_err != NULL) +    { +        printf("libui shat itself :( %s\n", ui_err); +        uiFreeInitError(ui_err); +        return 1; +    } + +    Config::Load(); + +    if      (Config::AudioVolume < 0)   Config::AudioVolume = 0; +    else if (Config::AudioVolume > 256) Config::AudioVolume = 256; + +    if (!Platform::LocalFileExists("bios7.bin") || +        !Platform::LocalFileExists("bios9.bin") || +        !Platform::LocalFileExists("firmware.bin")) +    { +#if defined(__WIN32__) || defined(UNIX_PORTABLE) +		const char* locationName = "the directory you run melonDS from"; +#else +		char* locationName = EmuDirectory; +#endif +		char msgboxtext[512]; +		sprintf(msgboxtext, +            "One or more of the following required files don't exist or couldn't be accessed:\n\n" +            "bios7.bin -- ARM7 BIOS\n" +            "bios9.bin -- ARM9 BIOS\n" +            "firmware.bin -- firmware image\n\n" +            "Dump the files from your DS and place them in %s.\n" +            "Make sure that the files can be accessed.", +			locationName +		); + +        uiMsgBoxError(NULL, "BIOS/Firmware not found", msgboxtext); + +        uiUninit(); +        SDL_Quit(); +        return 0; +    } +    if (!Platform::LocalFileExists("firmware.bin.bak")) +    { +        // verify the firmware +        // +        // there are dumps of an old hacked firmware floating around on the internet +        // and those are problematic +        // the hack predates WFC, and, due to this, any game that alters the WFC +        // access point data will brick that firmware due to it having critical +        // data in the same area. it has the same problem on hardware. +        // +        // but this should help stop users from reporting that issue over and over +        // again, when the issue is not from melonDS but from their firmware dump. +        // +        // I don't know about all the firmware hacks in existence, but the one I +        // looked at has 0x180 bytes from the header repeated at 0x3FC80, but +        // bytes 0x0C-0x14 are different. + +        FILE* f = Platform::OpenLocalFile("firmware.bin", "rb"); +        u8 chk1[0x180], chk2[0x180]; + +        fseek(f, 0, SEEK_SET); +        fread(chk1, 1, 0x180, f); +        fseek(f, -0x380, SEEK_END); +        fread(chk2, 1, 0x180, f); + +        memset(&chk1[0x0C], 0, 8); +        memset(&chk2[0x0C], 0, 8); + +        fclose(f); + +        if (!memcmp(chk1, chk2, 0x180)) +        { +            uiMsgBoxError(NULL, +                          "Problematic firmware dump", +                          "You are using an old hacked firmware dump.\n" +                          "Firmware boot will stop working if you run any game that alters WFC settings.\n\n" +                          "Note that the issue is not from melonDS, it would also happen on an actual DS."); +        } +    } +    { +        const char* romlist_missing = "Save memory type detection will not work correctly.\n\n" +            "You should use the latest version of romlist.bin (provided in melonDS release packages)."; +#if !defined(UNIX_PORTABLE) && !defined(__WIN32__) +        std::string missingstr = std::string(romlist_missing) + +            "\n\nThe ROM list should be placed in " + g_get_user_data_dir() + "/melonds/, otherwise " +            "melonDS will search for it in the current working directory."; +        const char* romlist_missing_text = missingstr.c_str(); +#else +        const char* romlist_missing_text = romlist_missing; +#endif + +        FILE* f = Platform::OpenDataFile("romlist.bin"); +        if (f) +        { +            u32 data; +            fread(&data, 4, 1, f); +            fclose(f); + +            if ((data >> 24) == 0) // old CRC-based list +            { +                uiMsgBoxError(NULL, "Your version of romlist.bin is outdated.", romlist_missing_text); +            } +        } +        else +        { +        	uiMsgBoxError(NULL, "romlist.bin not found.", romlist_missing_text); +        } +    } + +    CreateMainWindowMenu(); + +    MainDrawAreaHandler.Draw = OnAreaDraw; +    MainDrawAreaHandler.MouseEvent = OnAreaMouseEvent; +    MainDrawAreaHandler.MouseCrossed = OnAreaMouseCrossed; +    MainDrawAreaHandler.DragBroken = OnAreaDragBroken; +    MainDrawAreaHandler.KeyEvent = OnAreaKeyEvent; +    MainDrawAreaHandler.Resize = OnAreaResize; + +    WindowWidth = Config::WindowWidth; +    WindowHeight = Config::WindowHeight; + +    Screen_UseGL = Config::ScreenUseGL || (Config::_3DRenderer != 0); + +    GL_3DScale = Config::GL_ScaleFactor; +    if      (GL_3DScale < 1) GL_3DScale = 1; +    else if (GL_3DScale > 8) GL_3DScale = 8; + +    CreateMainWindow(Screen_UseGL); + +    ScreenRotation = Config::ScreenRotation; +    ScreenGap = Config::ScreenGap; +    ScreenLayout = Config::ScreenLayout; +    ScreenSizing = Config::ScreenSizing; + +#define SANITIZE(var, min, max)  if ((var < min) || (var > max)) var = 0; +    SANITIZE(ScreenRotation, 0, 3); +    SANITIZE(ScreenLayout, 0, 2); +    SANITIZE(ScreenSizing, 0, 3); +#undef SANITIZE + +    for (int i = 0; i < 9; i++) uiMenuItemDisable(MenuItem_SaveStateSlot[i]); +    for (int i = 0; i < 9; i++) uiMenuItemDisable(MenuItem_LoadStateSlot[i]); +    uiMenuItemDisable(MenuItem_UndoStateLoad); + +    uiMenuItemDisable(MenuItem_Pause); +    uiMenuItemDisable(MenuItem_Reset); +    uiMenuItemDisable(MenuItem_Stop); + +    uiMenuItemSetChecked(MenuItem_SavestateSRAMReloc, Config::SavestateRelocSRAM?1:0); + +    uiMenuItemSetChecked(MenuItem_ScreenRot[ScreenRotation], 1); +    uiMenuItemSetChecked(MenuItem_ScreenLayout[ScreenLayout], 1); +    uiMenuItemSetChecked(MenuItem_ScreenSizing[ScreenSizing], 1); + +    for (int i = 0; i < 6; i++) +    { +        if (ScreenGap == kScreenGap[i]) +            uiMenuItemSetChecked(MenuItem_ScreenGap[i], 1); +    } + +    OnSetScreenRotation(MenuItem_ScreenRot[ScreenRotation], MainWindow, (void*)&kScreenRot[ScreenRotation]); + +    uiMenuItemSetChecked(MenuItem_ScreenFilter, Config::ScreenFilter==1); +    uiMenuItemSetChecked(MenuItem_LimitFPS, Config::LimitFPS==1); +    uiMenuItemSetChecked(MenuItem_AudioSync, Config::AudioSync==1); +    uiMenuItemSetChecked(MenuItem_ShowOSD, Config::ShowOSD==1); + +#ifdef MELONCAP +    MelonCap::Init(); +#endif // MELONCAP + +    AudioSync = SDL_CreateCond(); +    AudioSyncLock = SDL_CreateMutex(); + +    AudioFreq = 48000; // TODO: make configurable? +    SDL_AudioSpec whatIwant, whatIget; +    memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); +    whatIwant.freq = AudioFreq; +    whatIwant.format = AUDIO_S16LSB; +    whatIwant.channels = 2; +    whatIwant.samples = 1024; +    whatIwant.callback = AudioCallback; +    AudioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); +    if (!AudioDevice) +    { +        printf("Audio init failed: %s\n", SDL_GetError()); +    } +    else +    { +        AudioFreq = whatIget.freq; +        printf("Audio output frequency: %d Hz\n", AudioFreq); +        SDL_PauseAudioDevice(AudioDevice, 1); +    } + +    memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); +    whatIwant.freq = 44100; +    whatIwant.format = AUDIO_S16LSB; +    whatIwant.channels = 1; +    whatIwant.samples = 1024; +    whatIwant.callback = MicCallback; +    MicDevice = SDL_OpenAudioDevice(NULL, 1, &whatIwant, &whatIget, 0); +    if (!MicDevice) +    { +        printf("Mic init failed: %s\n", SDL_GetError()); +        MicBufferLength = 0; +    } +    else +    { +        SDL_PauseAudioDevice(MicDevice, 1); +    } + +    memset(MicBuffer, 0, sizeof(MicBuffer)); +    MicBufferReadPos = 0; +    MicBufferWritePos = 0; + +    MicWavBuffer = NULL; +    if (Config::MicInputType == 3) MicLoadWav(Config::MicWavPath); + +    JoystickID = Config::JoystickID; +    Joystick = NULL; +    OpenJoystick(); + +    EmuRunning = 2; +    RunningSomething = false; +    EmuThread = SDL_CreateThread(EmuThreadFunc, "melonDS magic", NULL); + +    if (argc > 1) +    { +        char* file = argv[1]; +        char* ext = &file[strlen(file)-3]; + +        if (!strcasecmp(ext, "nds") || !strcasecmp(ext, "srl")) +        { +            strncpy(ROMPath[0], file, 1023); +            ROMPath[0][1023] = '\0'; + +            SetupSRAMPath(0); + +            if (NDS::LoadROM(ROMPath[0], SRAMPath[0], Config::DirectBoot)) +                Run(); +        } + +        if (argc > 2) +        { +            file = argv[2]; +            ext = &file[strlen(file)-3]; + +            if (!strcasecmp(ext, "gba")) +            { +                strncpy(ROMPath[1], file, 1023); +                ROMPath[1][1023] = '\0'; + +                SetupSRAMPath(1); + +                NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]); +            } +        } +    } + +    uiMain(); + +    if (Joystick) SDL_JoystickClose(Joystick); +    if (AudioDevice) SDL_CloseAudioDevice(AudioDevice); +    if (MicDevice)   SDL_CloseAudioDevice(MicDevice); + +    SDL_DestroyCond(AudioSync); +    SDL_DestroyMutex(AudioSyncLock); + +    if (MicWavBuffer) delete[] MicWavBuffer; + +#ifdef MELONCAP +    MelonCap::DeInit(); +#endif // MELONCAP + +    if (ScreenBitmap[0]) uiDrawFreeBitmap(ScreenBitmap[0]); +    if (ScreenBitmap[1]) uiDrawFreeBitmap(ScreenBitmap[1]); + +    Config::ScreenRotation = ScreenRotation; +    Config::ScreenGap = ScreenGap; +    Config::ScreenLayout = ScreenLayout; +    Config::ScreenSizing = ScreenSizing; + +    Config::Save(); + +    uiUninit(); +    SDL_Quit(); +    delete[] EmuDirectory; +    return 0; +} + +#ifdef __WIN32__ + +#include <windows.h> + +int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdshow) +{ +    int argc = 0; +    wchar_t** argv_w = CommandLineToArgvW(GetCommandLineW(), &argc); +    char* nullarg = ""; + +    char** argv = new char*[argc]; +    for (int i = 0; i < argc; i++) +    { +        int len = WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, NULL, 0, NULL, NULL); +        if (len < 1) return NULL; +        argv[i] = new char[len]; +        int res = WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, argv[i], len, NULL, NULL); +        if (res != len) { delete[] argv[i]; argv[i] = nullarg; } +    } + +    if (AttachConsole(ATTACH_PARENT_PROCESS)) +    { +        freopen("CONOUT$", "w", stdout); +        freopen("CONOUT$", "w", stderr); +        printf("\n"); +    } + +    int ret = main(argc, argv); + +    printf("\n\n>"); + +    for (int i = 0; i < argc; i++) if (argv[i] != nullarg) delete[] argv[i]; +    delete[] argv; + +    return ret; +} + +#endif |