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 |