diff --git a/emulator/emulator.hpp b/emulator/emulator.hpp index ecf689bc..66276783 100644 --- a/emulator/emulator.hpp +++ b/emulator/emulator.hpp @@ -3,7 +3,7 @@ namespace Emulator { static const char Name[] = "higan"; - static const char Version[] = "093.03"; + static const char Version[] = "093.04"; static const char Author[] = "byuu"; static const char License[] = "GPLv3"; static const char Website[] = "http://byuu.org/"; diff --git a/phoenix/core/core.cpp b/phoenix/core/core.cpp index 38127f24..33f9ce41 100644 --- a/phoenix/core/core.cpp +++ b/phoenix/core/core.cpp @@ -15,7 +15,6 @@ using namespace nall; namespace phoenix { #include "state.hpp" - #include "utility.cpp" #include "layout/fixed-layout.cpp" #include "layout/horizontal-layout.cpp" #include "layout/vertical-layout.cpp" diff --git a/phoenix/core/utility.cpp b/phoenix/core/utility.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/phoenix/gtk/platform.hpp b/phoenix/gtk/platform.hpp index ea8c6012..aafb018e 100644 --- a/phoenix/gtk/platform.hpp +++ b/phoenix/gtk/platform.hpp @@ -243,7 +243,6 @@ struct pWidget : public pSizable { GtkWidget* gtkParent = nullptr; virtual GtkWidget* container(Widget& widget); - virtual Position containerOffset(); virtual bool focused(); virtual Size minimumSize(); virtual void setEnabled(bool enabled); @@ -545,7 +544,6 @@ struct pTabFrame : public pWidget { void append(string text, const image& image); GtkWidget* container(Widget& widget); - Position containerOffset(); Position displacement(); void remove(unsigned selection); void setEnabled(bool enabled); diff --git a/phoenix/gtk/utility.cpp b/phoenix/gtk/utility.cpp index 8f04f28e..64f08dcf 100644 --- a/phoenix/gtk/utility.cpp +++ b/phoenix/gtk/utility.cpp @@ -73,10 +73,6 @@ static Widget* GetParentWidget(Sizable* sizable) { return nullptr; } -static bool HasParentWidget(Sizable* sizable) { - return GetParentWidget(sizable) != nullptr; -} - static Keyboard::Keycode Keysym(unsigned keysym) { switch(keysym) { case GDK_Escape: return Keyboard::Keycode::Escape; diff --git a/phoenix/gtk/widget/tab-frame.cpp b/phoenix/gtk/widget/tab-frame.cpp index fc021044..ab9ef66b 100644 --- a/phoenix/gtk/widget/tab-frame.cpp +++ b/phoenix/gtk/widget/tab-frame.cpp @@ -38,10 +38,6 @@ GtkWidget* pTabFrame::container(Widget& widget) { return nullptr; } -Position pTabFrame::containerOffset() { - return {widget.state.geometry.x + 3, widget.state.geometry.y + 28}; -} - Position pTabFrame::displacement() { return {6, 31}; } @@ -63,8 +59,7 @@ void pTabFrame::setGeometry(Geometry geometry) { geometry.x += 1, geometry.width -= 5; geometry.y += 26, geometry.height -= 31; for(auto& layout : tabFrame.state.layout) { - if(layout == nullptr) continue; - layout->setGeometry(geometry); + if(layout) layout->setGeometry(geometry); } synchronizeLayout(); } diff --git a/phoenix/gtk/widget/widget.cpp b/phoenix/gtk/widget/widget.cpp index 9f1f1bad..1b834d32 100644 --- a/phoenix/gtk/widget/widget.cpp +++ b/phoenix/gtk/widget/widget.cpp @@ -4,10 +4,6 @@ GtkWidget* pWidget::container(Widget& widget) { return nullptr; } -Position pWidget::containerOffset() { - return {0, 0}; -} - bool pWidget::focused() { return GTK_WIDGET_HAS_FOCUS(gtkWidget); } diff --git a/phoenix/gtk/window.cpp b/phoenix/gtk/window.cpp index 5b77e161..7acb0c39 100644 --- a/phoenix/gtk/window.cpp +++ b/phoenix/gtk/window.cpp @@ -153,7 +153,7 @@ void pWindow::append(Widget& widget) { widget.setFont(window.state.widgetFont); } - if(HasParentWidget(&widget)) { + if(GetParentWidget(&widget)) { widget.p.gtkParent = GetParentWidget(&widget)->p.container(widget); } else { widget.p.gtkParent = formContainer; diff --git a/phoenix/qt/platform.moc b/phoenix/qt/platform.moc index f0f87005..1ec403eb 100644 --- a/phoenix/qt/platform.moc +++ b/phoenix/qt/platform.moc @@ -1,7 +1,7 @@ /**************************************************************************** ** Meta object code from reading C++ file 'platform.moc.hpp' ** -** Created: Fri Nov 22 09:50:07 2013 +** Created: Sun Nov 24 07:06:37 2013 ** by: The Qt Meta Object Compiler version 63 (Qt 4.8.2) ** ** WARNING! All changes made in this file will be lost! diff --git a/phoenix/qt/platform.moc.hpp b/phoenix/qt/platform.moc.hpp index 8ea492bf..7f711e17 100644 --- a/phoenix/qt/platform.moc.hpp +++ b/phoenix/qt/platform.moc.hpp @@ -127,8 +127,6 @@ public: QStatusBar* qtStatus; QWidget* qtContainer; - static Window& none(); - void append(Layout& layout); void append(Menu& menu); void append(Widget& widget); @@ -255,6 +253,8 @@ public slots: struct pSizable : public pObject { Sizable& sizable; + virtual Position displacement() { return {0, 0}; } + pSizable(Sizable& sizable) : pObject(sizable), sizable(sizable) {} void constructor() {} @@ -274,6 +274,7 @@ struct pWidget : public pSizable { Widget& widget; QWidget* qtWidget; + virtual QWidget* container(Widget& widget); bool focused(); virtual Size minimumSize(); virtual void setEnabled(bool enabled); @@ -664,6 +665,8 @@ public: QTabWidget* qtTabFrame; void append(string text, const image& image); + QWidget* container(Widget& widget); + Position displacement(); void remove(unsigned selection); void setEnabled(bool enabled); void setGeometry(Geometry geometry); diff --git a/phoenix/qt/utility.cpp b/phoenix/qt/utility.cpp index 91e16191..bea91884 100644 --- a/phoenix/qt/utility.cpp +++ b/phoenix/qt/utility.cpp @@ -23,6 +23,33 @@ static lstring DropPaths(QDropEvent* event) { return paths; } +static Position GetDisplacement(Sizable* sizable) { + Position position; + while(sizable->state.parent) { + Position displacement = sizable->state.parent->p.displacement(); + position.x += displacement.x; + position.y += displacement.y; + sizable = sizable->state.parent; + } + return position; +} + +static Layout* GetParentWidgetLayout(Sizable* sizable) { + while(sizable) { + if(sizable->state.parent && dynamic_cast(sizable->state.parent)) return (Layout*)sizable; + sizable = sizable->state.parent; + } + return nullptr; +} + +static Widget* GetParentWidget(Sizable* sizable) { + while(sizable) { + if(sizable->state.parent && dynamic_cast(sizable->state.parent)) return (Widget*)sizable->state.parent; + sizable = sizable->state.parent; + } + return nullptr; +} + static Keyboard::Keycode Keysym(int keysym) { switch(keysym) { case XK_Escape: return Keyboard::Keycode::Escape; diff --git a/phoenix/qt/widget/frame.cpp b/phoenix/qt/widget/frame.cpp index 25eb2c21..77edb5a9 100644 --- a/phoenix/qt/widget/frame.cpp +++ b/phoenix/qt/widget/frame.cpp @@ -10,7 +10,7 @@ void pFrame::setGeometry(Geometry geometry) { if(frame.state.layout == nullptr) return; Size size = pFont::size(widget.state.font, frame.state.text); if(frame.state.text.empty()) size.height = 8; - geometry.x += 1, geometry.width -= 3; + geometry.x += 1, geometry.width -= 2; geometry.y += size.height, geometry.height -= size.height + 1; frame.state.layout->setGeometry(geometry); } @@ -26,6 +26,14 @@ void pFrame::setVisible(bool visible) { void pFrame::constructor() { qtWidget = qtFrame = new QGroupBox; + if(QApplication::style()->objectName() == "gtk+") { + //QGtkStyle (gtk+) theme disrespects font weight and omits the border, even if native GTK+ theme does not + //bold Label controls already exist; so this style sheet forces QGtkStyle to look like a Frame instead + qtFrame->setStyleSheet( + "QGroupBox { border: 1px solid #aaa; border-radius: 5px; margin-top: 0.5em; }\n" + "QGroupBox::title { left: 5px; subcontrol-origin: margin; }\n" + ); + } pWidget::synchronizeState(); setText(frame.state.text); diff --git a/phoenix/qt/widget/tab-frame.cpp b/phoenix/qt/widget/tab-frame.cpp index 14f04a53..4eb5e289 100644 --- a/phoenix/qt/widget/tab-frame.cpp +++ b/phoenix/qt/widget/tab-frame.cpp @@ -2,11 +2,24 @@ namespace phoenix { void pTabFrame::append(string text, const image& image) { unsigned selection = tabFrame.state.text.size() - 1; - QWidget* widget = new QWidget; //addTab() requires a child widget, so give it an empty one - qtTabFrame->addTab(widget, QString::fromUtf8(text)); + qtTabFrame->addTab(new QWidget, QString::fromUtf8(text)); if(!image.empty()) setImage(selection, image); } +QWidget* pTabFrame::container(Widget& widget) { + Layout* widgetLayout = GetParentWidgetLayout(&widget); + unsigned selection = 0; + for(auto& layout : tabFrame.state.layout) { + if(layout == widgetLayout) return qtTabFrame->widget(selection); + selection++; + } + return nullptr; +} + +Position pTabFrame::displacement() { + return {5, 33}; +} + void pTabFrame::remove(unsigned selection) { qtTabFrame->removeTab(selection); } @@ -20,11 +33,10 @@ void pTabFrame::setEnabled(bool enabled) { void pTabFrame::setGeometry(Geometry geometry) { pWidget::setGeometry(geometry); - geometry.x += 1, geometry.width -= 2; - geometry.y += 29, geometry.height -= 30; + geometry.x += 0, geometry.width -= 5; + geometry.y += 29, geometry.height -= 33; for(auto& layout : tabFrame.state.layout) { - if(layout == nullptr) continue; - layout->setGeometry(geometry); + if(layout) layout->setGeometry(geometry); } synchronizeLayout(); } diff --git a/phoenix/qt/widget/widget.cpp b/phoenix/qt/widget/widget.cpp index 22e368d7..6cddbcdf 100644 --- a/phoenix/qt/widget/widget.cpp +++ b/phoenix/qt/widget/widget.cpp @@ -1,5 +1,9 @@ namespace phoenix { +QWidget* pWidget::container(Widget& widget) { + return nullptr; +} + bool pWidget::focused() { return qtWidget->hasFocus(); } @@ -24,6 +28,10 @@ void pWidget::setFont(string font) { } void pWidget::setGeometry(Geometry geometry) { + Position displacement = GetDisplacement(&widget); + geometry.x -= displacement.x; + geometry.y -= displacement.y; + qtWidget->setGeometry(geometry.x, geometry.y, geometry.width, geometry.height); if(widget.onSize) widget.onSize(); } diff --git a/phoenix/qt/window.cpp b/phoenix/qt/window.cpp index 47ad11a4..c8a9aab8 100644 --- a/phoenix/qt/window.cpp +++ b/phoenix/qt/window.cpp @@ -1,11 +1,5 @@ namespace phoenix { -Window& pWindow::none() { - static Window* window = nullptr; - if(window == nullptr) window = new Window; - return *window; -} - void pWindow::append(Layout& layout) { Geometry geometry = window.state.geometry; geometry.x = geometry.y = 0; @@ -14,7 +8,7 @@ void pWindow::append(Layout& layout) { void pWindow::append(Menu& menu) { if(window.state.menuFont != "") menu.p.setFont(window.state.menuFont); - else menu.p.setFont("Sans, 8"); + else menu.p.setFont(Font::sans(8)); qtMenu->addMenu(menu.p.qtMenu); } @@ -22,8 +16,12 @@ void pWindow::append(Widget& widget) { if(widget.font().empty() && !window.state.widgetFont.empty()) { widget.setFont(window.state.widgetFont); } - if(widget.font().empty()) widget.p.setFont("Sans, 8"); - widget.p.qtWidget->setParent(qtContainer); + if(widget.font().empty()) widget.p.setFont(Font::sans(8)); + if(GetParentWidget(&widget)) { + widget.p.qtWidget->setParent(GetParentWidget(&widget)->p.container(widget)); + } else { + widget.p.qtWidget->setParent(qtContainer); + } widget.setVisible(widget.visible()); } @@ -69,10 +67,11 @@ void pWindow::remove(Widget& widget) { void pWindow::setBackgroundColor(Color color) { QPalette palette; - palette.setColor(QPalette::Window, QColor(color.red, color.green, color.blue, color.alpha)); + palette.setColor(QPalette::Background, QColor(color.red, color.green, color.blue /*, color.alpha */)); qtContainer->setPalette(palette); qtContainer->setAutoFillBackground(true); - qtWindow->setAttribute(Qt::WA_TranslucentBackground, color.alpha != 255); + //translucency results are very unpleasant without a compositor; so disable for now + //qtWindow->setAttribute(Qt::WA_TranslucentBackground, color.alpha != 255); } void pWindow::setDroppable(bool droppable) { diff --git a/phoenix/windows/application.cpp b/phoenix/windows/application.cpp index 0d346a15..cd04d5f4 100644 --- a/phoenix/windows/application.cpp +++ b/phoenix/windows/application.cpp @@ -213,22 +213,6 @@ static LRESULT CALLBACK Application_windowProc(HWND hwnd, UINT msg, WPARAM wpara case WM_ERASEBKGND: if(window.p.onEraseBackground()) return true; break; case WM_ENTERMENULOOP: case WM_ENTERSIZEMOVE: window.p.onModalBegin(); return FALSE; case WM_EXITMENULOOP: case WM_EXITSIZEMOVE: window.p.onModalEnd(); return FALSE; - - case WM_CTLCOLORBTN: - case WM_CTLCOLORSTATIC: { - Object* object = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); - if(object == nullptr) break; - if(dynamic_cast(object) || dynamic_cast(object) || dynamic_cast(object)) { - //text edit controls, when disabled, use CTLCOLORSTATIC instead of CTLCOLOREDIT - //override this behavior: we do not want read-only edit controls to use the parent window background color - return DefWindowProc(hwnd, WM_CTLCOLOREDIT, wparam, lparam); - } else if(window.p.brush) { - HDC hdc = (HDC)wparam; - SetBkColor((HDC)wparam, window.p.brushColor); - return (INT_PTR)window.p.brush; - } - break; - } } return Shared_windowProc(DefWindowProc, hwnd, msg, wparam, lparam); diff --git a/phoenix/windows/header.hpp b/phoenix/windows/header.hpp index d561f87a..43717c23 100644 --- a/phoenix/windows/header.hpp +++ b/phoenix/windows/header.hpp @@ -14,3 +14,5 @@ #include #include #include + +#define TBS_TRANSPARENTBKGND 0x1000 diff --git a/phoenix/windows/platform.hpp b/phoenix/windows/platform.hpp index 0cc2a8fc..dbc7952a 100644 --- a/phoenix/windows/platform.hpp +++ b/phoenix/windows/platform.hpp @@ -375,8 +375,8 @@ struct pFrame : public pWidget { struct pHexEdit : public pWidget { HexEdit& hexEdit; - HWND scrollBar; - LRESULT CALLBACK (*windowProc)(HWND, UINT, LPARAM, WPARAM); + WindowProc windowProc = nullptr; + HWND scrollBar = nullptr; void setColumns(unsigned columns); void setLength(unsigned length); diff --git a/phoenix/windows/utility.cpp b/phoenix/windows/utility.cpp index f7f76de2..051c9de9 100644 --- a/phoenix/windows/utility.cpp +++ b/phoenix/windows/utility.cpp @@ -74,6 +74,19 @@ static unsigned GetWindowZOrder(HWND hwnd) { return z; } +static void ImageList_Append(HIMAGELIST imageList, const nall::image& source, unsigned scale) { + auto image = source; + if(image.empty()) { + image.allocate(scale, scale); + image.fill(GetSysColor(COLOR_WINDOW)); + } + image.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + image.scale(scale, scale); + HBITMAP bitmap = CreateBitmap(image); + ImageList_Add(imageList, bitmap, NULL); + DeleteObject(bitmap); +} + static Keyboard::Keycode Keysym(unsigned keysym, unsigned keyflags) { #define pressed(keysym) (GetAsyncKeyState(keysym) & 0x8000) #define enabled(keysym) (GetKeyState(keysym)) @@ -231,12 +244,34 @@ static LRESULT CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT if(process == false) return DefWindowProc(hwnd, msg, wparam, lparam); switch(msg) { + case WM_CTLCOLORBTN: + case WM_CTLCOLORSTATIC: { + Object* object = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); + if(object == nullptr) break; + if(dynamic_cast(object) || dynamic_cast(object) || dynamic_cast(object)) { + //text edit controls, when disabled, use CTLCOLORSTATIC instead of CTLCOLOREDIT + //override this behavior: we do not want read-only edit controls to use the parent window background color + return windowProc(hwnd, WM_CTLCOLOREDIT, wparam, lparam); + } else if(!GetParentWidget((Sizable*)object) && window.p.brush) { + SetBkColor((HDC)wparam, window.p.brushColor); + return (INT_PTR)window.p.brush; + }/* else { + //this will repaint the background properly, but the foreground isn't always rendered after ... + RECT rc; + GetClientRect((HWND)lparam, &rc); + DrawThemeParentBackground((HWND)lparam, (HDC)wparam, &rc); + SetBkMode((HDC)wparam, TRANSPARENT); + return (INT_PTR)GetStockBrush(HOLLOW_BRUSH); + }*/ + break; + } + case WM_DRAWITEM: { unsigned id = LOWORD(wparam); HWND control = GetDlgItem(hwnd, id); Object* object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); if(object == nullptr) break; - if(dynamic_cast(object)) { ((TabFrame*)object)->p.onDrawItem(lparam); break; } + if(dynamic_cast(object)) { ((TabFrame*)object)->p.onDrawItem(lparam); return TRUE; } break; } @@ -245,16 +280,16 @@ static LRESULT CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT HWND control = GetDlgItem(hwnd, id); Object* object = control ? (Object*)GetWindowLongPtr(control, GWLP_USERDATA) : (Object*)(&pObject::find(id)->object); if(object == nullptr) break; - if(dynamic_cast(object)) { ((Item*)object)->p.onActivate(); break; } - if(dynamic_cast(object)) { ((CheckItem*)object)->p.onToggle(); break; } - if(dynamic_cast(object)) { ((Button*)object)->p.onActivate(); break; } - if(dynamic_cast(object)) { ((CheckButton*)object)->p.onToggle(); break; } - if(dynamic_cast(object)) { ((CheckLabel*)object)->p.onToggle(); break; } - if(dynamic_cast(object) && HIWORD(wparam) == CBN_SELCHANGE) { ((ComboButton*)object)->p.onChange(); break; } - if(dynamic_cast(object) && HIWORD(wparam) == EN_CHANGE) { ((LineEdit*)object)->p.onChange(); break; } - if(dynamic_cast(object)) { ((RadioButton*)object)->p.onActivate(); break; } - if(dynamic_cast(object)) { ((RadioLabel*)object)->p.onActivate(); break; } - if(dynamic_cast(object) && HIWORD(wparam) == EN_CHANGE) { ((TextEdit*)object)->p.onChange(); break; } + if(dynamic_cast(object)) { ((Item*)object)->p.onActivate(); return FALSE; } + if(dynamic_cast(object)) { ((CheckItem*)object)->p.onToggle(); return FALSE; } + if(dynamic_cast(object)) { ((Button*)object)->p.onActivate(); return FALSE; } + if(dynamic_cast(object)) { ((CheckButton*)object)->p.onToggle(); return FALSE; } + if(dynamic_cast(object)) { ((CheckLabel*)object)->p.onToggle(); return FALSE; } + if(dynamic_cast(object) && HIWORD(wparam) == CBN_SELCHANGE) { ((ComboButton*)object)->p.onChange(); return FALSE; } + if(dynamic_cast(object) && HIWORD(wparam) == EN_CHANGE) { ((LineEdit*)object)->p.onChange(); return FALSE; } + if(dynamic_cast(object)) { ((RadioButton*)object)->p.onActivate(); return FALSE; } + if(dynamic_cast(object)) { ((RadioLabel*)object)->p.onActivate(); return FALSE; } + if(dynamic_cast(object) && HIWORD(wparam) == EN_CHANGE) { ((TextEdit*)object)->p.onChange(); return FALSE; } break; } @@ -283,8 +318,8 @@ static LRESULT CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT if(object == nullptr) break; if(dynamic_cast(object)) { ((HorizontalScroller*)object)->p.onChange(wparam); return TRUE; } if(dynamic_cast(object)) { ((VerticalScroller*)object)->p.onChange(wparam); return TRUE; } - if(dynamic_cast(object)) { ((HorizontalSlider*)object)->p.onChange(); break; } - if(dynamic_cast(object)) { ((VerticalSlider*)object)->p.onChange(); break; } + if(dynamic_cast(object)) { ((HorizontalSlider*)object)->p.onChange(); return TRUE; } + if(dynamic_cast(object)) { ((VerticalSlider*)object)->p.onChange(); return TRUE; } break; } } diff --git a/phoenix/windows/widget/combo-button.cpp b/phoenix/windows/widget/combo-button.cpp index 26b3173f..3455a4ad 100644 --- a/phoenix/windows/widget/combo-button.cpp +++ b/phoenix/windows/widget/combo-button.cpp @@ -24,7 +24,7 @@ void pComboButton::reset() { } void pComboButton::setGeometry(Geometry geometry) { - SetWindowPos(hwnd, NULL, geometry.x, geometry.y, geometry.width, 1, SWP_NOZORDER); + pWidget::setGeometry(geometry); RECT rc; GetWindowRect(hwnd, &rc); unsigned adjustedHeight = geometry.height - ((rc.bottom - rc.top) - SendMessage(hwnd, CB_GETITEMHEIGHT, (WPARAM)-1, 0)); diff --git a/phoenix/windows/widget/hex-edit.cpp b/phoenix/windows/widget/hex-edit.cpp index e39faf78..8f5ea1bd 100644 --- a/phoenix/windows/widget/hex-edit.cpp +++ b/phoenix/windows/widget/hex-edit.cpp @@ -4,7 +4,6 @@ static LRESULT CALLBACK HexEdit_windowProc(HWND hwnd, UINT msg, WPARAM wparam, L HexEdit& hexEdit = *(HexEdit*)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch(msg) { - case WM_KEYDOWN: if(hexEdit.p.keyPress(wparam)) return 0; break; @@ -45,7 +44,6 @@ static LRESULT CALLBACK HexEdit_windowProc(HWND hwnd, UINT msg, WPARAM wparam, L hexEdit.p.scrollTo(info.nPos); return TRUE; } - } return hexEdit.p.windowProc(hwnd, msg, wparam, lparam); @@ -123,7 +121,7 @@ void pHexEdit::constructor() { ); SetWindowLongPtr(scrollBar, GWLP_USERDATA, (LONG_PTR)&hexEdit); - windowProc = (LRESULT CALLBACK (*)(HWND, UINT, LPARAM, WPARAM))GetWindowLongPtr(hwnd, GWLP_WNDPROC); + windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC); SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)HexEdit_windowProc); setDefaultFont(); diff --git a/phoenix/windows/widget/horizontal-slider.cpp b/phoenix/windows/widget/horizontal-slider.cpp index 68547543..310047f6 100644 --- a/phoenix/windows/widget/horizontal-slider.cpp +++ b/phoenix/windows/widget/horizontal-slider.cpp @@ -17,10 +17,11 @@ void pHorizontalSlider::setPosition(unsigned position) { void pHorizontalSlider::constructor() { hwnd = CreateWindow( - TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_NOTICKS | TBS_BOTH | TBS_HORZ, + TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_TRANSPARENTBKGND | TBS_NOTICKS | TBS_BOTH | TBS_HORZ, 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 ); SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&horizontalSlider); + unsigned position = horizontalSlider.state.position; setLength(horizontalSlider.state.length); horizontalSlider.setPosition(position); diff --git a/phoenix/windows/widget/label.cpp b/phoenix/windows/widget/label.cpp index 335b9177..d2345bdc 100644 --- a/phoenix/windows/widget/label.cpp +++ b/phoenix/windows/widget/label.cpp @@ -36,19 +36,13 @@ static LRESULT CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPA Window* window = (Window*)label->Sizable::state.window; if(window == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); - if(msg == WM_GETDLGCODE) { - return DLGC_STATIC | DLGC_WANTCHARS; - } - - if(msg == WM_ERASEBKGND) { - //background is erased during WM_PAINT to prevent flickering - return TRUE; - } - - if(msg == WM_PAINT) { + switch(msg) { + case WM_GETDLGCODE: return DLGC_STATIC | DLGC_WANTCHARS; + case WM_ERASEBKGND: return TRUE; + case WM_PAINT: { PAINTSTRUCT ps; - RECT rc; BeginPaint(hwnd, &ps); + RECT rc; GetClientRect(hwnd, &rc); DrawThemeParentBackground(hwnd, ps.hdc, &rc); SetBkMode(ps.hdc, TRANSPARENT); @@ -64,6 +58,8 @@ static LRESULT CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPA rc.bottom = rc.top + height; DrawText(ps.hdc, text, -1, &rc, DT_LEFT | DT_END_ELLIPSIS); EndPaint(hwnd, &ps); + return FALSE; + } } return DefWindowProc(hwnd, msg, wparam, lparam); diff --git a/phoenix/windows/widget/list-view.cpp b/phoenix/windows/widget/list-view.cpp index 79c8534c..67df09d2 100644 --- a/phoenix/windows/widget/list-view.cpp +++ b/phoenix/windows/widget/list-view.cpp @@ -23,19 +23,6 @@ void ListView_SetImage(HWND hwnd, HIMAGELIST imageList, unsigned row, unsigned c ListView_SetItem(hwnd, &item); } -void ImageList_Append(HIMAGELIST imageList, const nall::image& source) { - auto image = source; - if(image.empty()) { - image.allocate(15, 15); - image.fill(GetSysColor(COLOR_WINDOW)); - } - image.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); - image.scale(15, 15, Interpolation::Linear); - HBITMAP bitmap = CreateBitmap(image); - ImageList_Add(imageList, bitmap, NULL); - DeleteObject(bitmap); -} - void pListView::append(const lstring& list) { wchar_t empty[] = L""; unsigned row = ListView_GetItemCount(hwnd); @@ -122,7 +109,7 @@ void pListView::setImage(unsigned selection, unsigned position, const image& ima //append and assign new image imageMap(selection)(position) = images.size(); images.append(image); - ImageList_Append(imageList, image); + ImageList_Append(imageList, image, 15); ListView_SetImage(hwnd, imageList, selection, position, imageMap(selection)(position)); } @@ -211,7 +198,7 @@ void pListView::buildImageList() { } //build image list - for(auto& imageItem : images) ImageList_Append(imageList, imageItem); + for(auto& imageItem : images) ImageList_Append(imageList, imageItem, 15); if(images.size() <= 1) return; //set images for all cells diff --git a/phoenix/windows/widget/tab-frame.cpp b/phoenix/windows/widget/tab-frame.cpp index 9a3abb64..f447586e 100644 --- a/phoenix/windows/widget/tab-frame.cpp +++ b/phoenix/windows/widget/tab-frame.cpp @@ -94,7 +94,7 @@ void pTabFrame::buildImageList() { unsigned size = pFont::size(hfont, " ").height; imageList = ImageList_Create(size, size, ILC_COLOR32, 1, 0); for(auto& image : tabFrame.state.image) { - ImageList_Append(imageList, image); + ImageList_Append(imageList, image, size); } TabCtrl_SetImageList(hwnd, imageList); for(unsigned n = 0; n < tabFrame.state.image.size(); n++) { diff --git a/phoenix/windows/widget/vertical-slider.cpp b/phoenix/windows/widget/vertical-slider.cpp index 03bde0ed..006526c7 100644 --- a/phoenix/windows/widget/vertical-slider.cpp +++ b/phoenix/windows/widget/vertical-slider.cpp @@ -17,10 +17,11 @@ void pVerticalSlider::setPosition(unsigned position) { void pVerticalSlider::constructor() { hwnd = CreateWindow( - TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_NOTICKS | TBS_BOTH | TBS_VERT, + TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_TRANSPARENTBKGND | TBS_NOTICKS | TBS_BOTH | TBS_VERT, 0, 0, 0, 0, parentHwnd, (HMENU)id, GetModuleHandle(0), 0 ); SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&verticalSlider); + unsigned position = verticalSlider.state.position; setLength(verticalSlider.state.length); verticalSlider.setPosition(position); diff --git a/target-ethos/configuration/configuration.cpp b/target-ethos/configuration/configuration.cpp index b9072821..48bd32a3 100644 --- a/target-ethos/configuration/configuration.cpp +++ b/target-ethos/configuration/configuration.cpp @@ -41,6 +41,10 @@ ConfigurationSettings::ConfigurationSettings() { server.append(server.password = "", "Password"); append(server, "Server"); + library.append(library.selection = 0, "Selection"); + library.append(library.showOnStartup = true, "ShowOnStartup"); + append(library, "Library"); + load(); } diff --git a/target-ethos/configuration/configuration.hpp b/target-ethos/configuration/configuration.hpp index 8ed0576e..252f923c 100644 --- a/target-ethos/configuration/configuration.hpp +++ b/target-ethos/configuration/configuration.hpp @@ -45,6 +45,11 @@ struct ConfigurationSettings : Configuration::Document { string password; } server; + struct Library : Configuration::Node { + unsigned selection; + bool showOnStartup; + } library; + void load(); void save(); ConfigurationSettings(); diff --git a/target-ethos/ethos.cpp b/target-ethos/ethos.cpp index 4b74f522..833861ee 100644 --- a/target-ethos/ethos.cpp +++ b/target-ethos/ethos.cpp @@ -66,7 +66,7 @@ Program::Program(int argc, char** argv) { utility = new Utility; inputManager = new InputManager; windowManager = new WindowManager; - browser = new Browser; + libraryManager = new LibraryManager; presentation = new Presentation; dipSwitches = new DipSwitches; videoSettings = new VideoSettings; @@ -85,6 +85,8 @@ Program::Program(int argc, char** argv) { presentation->setVisible(); utility->resize(); + if(config->library.showOnStartup) libraryManager->setVisible(); + video.set(Video::Handle, presentation->viewport.handle()); if(!video.cap(Video::Depth) || !video.set(Video::Depth, depth = 30u)) { video.set(Video::Depth, depth = 24u); @@ -114,7 +116,6 @@ Program::Program(int argc, char** argv) { utility->unload(); config->save(); - browser->saveConfiguration(); inputManager->saveConfiguration(); windowManager->saveGeometry(); diff --git a/target-ethos/general/browser.cpp b/target-ethos/general/browser.cpp deleted file mode 100644 index afe9201c..00000000 --- a/target-ethos/general/browser.cpp +++ /dev/null @@ -1,178 +0,0 @@ -Browser* browser = nullptr; - -Browser::Browser() { - bootstrap(); - setGeometry({128, 128, 640, 400}); - windowManager->append(this, "Browser"); - - layout.setMargin(5); - homeButton.setImage({resource::home, sizeof resource::home}); - upButton.setImage({resource::up, sizeof resource::up}); - openButton.setText("Open"); - - append(layout); - layout.append(pathLayout, {~0, 0}, 5); - pathLayout.append(pathEdit, {~0, 0}, 5); - pathLayout.append(homeButton, {28, 28}, 5); - pathLayout.append(upButton, {28, 28}); - layout.append(fileList, {~0, ~0}, 5); - layout.append(controlLayout, {~0, 0}); - controlLayout.append(filterLabel, {~0, 0}, 5); - controlLayout.append(openButton, {80, 0}); - - pathEdit.onActivate = [&] { - string path = pathEdit.text(); - path.transform("\\", "/"); - if(path.endswith("/") == false) path.append("/"); - setPath(path); - }; - - homeButton.onActivate = [&] { - string libraryPath = string::read({configpath(), "higan/library.bml"}).strip().ltrim<1>("Path: ").transform("\\", "/"); - if(libraryPath.empty()) libraryPath = {userpath(), "Emulation/"}; - if(libraryPath.endswith("/") == false) libraryPath.append("/"); - setPath(libraryPath); - }; - - upButton.onActivate = [&] { - setPath(parentdir(path)); - }; - - fileList.onChange = {&Browser::synchronize, this}; - fileList.onActivate = openButton.onActivate = {&Browser::fileListActivate, this}; - - onClose = [&] { - setModal(false); - setVisible(false); - }; - - synchronize(); -} - -void Browser::synchronize() { - openButton.setEnabled(fileList.selected()); - if(fileList.selected()) { - for(auto& folder : folderList) { - if(folder.extension == extension) { - folder.selection = fileList.selection(); - } - } - } -} - -void Browser::saveConfiguration() { - config.save(program->path("paths.bml")); -} - -void Browser::bootstrap() { - for(auto& emulator : program->emulator) { - for(auto& media : emulator->media) { - bool found = false; - for(auto& folder : folderList) { - if(folder.extension == media.type) { - found = true; - break; - } - } - if(found == true) continue; - - Folder folder; - folder.extension = media.type; - folder.path = {userpath(), "Emulation/", media.name, "/"}; - folder.selection = 0; - folderList.append(folder); - } - } - - for(auto& folder : folderList) { - Configuration::Node node; - node.append(folder.path, "Path"); - node.append(folder.selection, "Selection"); - config.append(node, folder.extension); - } - - config.load(program->path("paths.bml")); - config.save(program->path("paths.bml")); -} - -string Browser::select(string title, string extension) { - this->extension = extension; - - string path; - unsigned selection = 0; - for(auto& folder : folderList) { - if(folder.extension == extension) { - path = folder.path; - selection = folder.selection; - break; - } - } - if(path.empty()) path = program->basepath; - setPath(path, selection); - - filterLabel.setText({"Filter: *.", extension}); - - audio.clear(); - setTitle(title); - setVisible(true); - fileList.setFocused(); - outputFilename = ""; - - setModal(); - return outputFilename; -} - -void Browser::setPath(string path, unsigned selection) { - //save path for next browser selection - for(auto& folder : folderList) { - if(folder.extension == extension) folder.path = path; - } - - this->path = path; - pathEdit.setText(path); - - fileList.reset(); - filenameList.reset(); - - lstring contents = directory::ifolders(path); - - for(auto& filename : contents) { - string suffix = {".", this->extension, "/"}; - if(filename.endswith("/") && !filename.endswith(suffix)) { - string name = filename; - name.rtrim<1>("/"); - fileList.append(name); - fileList.setImage(filenameList.size(), 0, {resource::folder, sizeof resource::folder}); - filenameList.append(filename); - } - } - - for(auto& filename : contents) { - string suffix = {".", this->extension, "/"}; - if(filename.endswith(suffix)) { - string name = filename; - name.rtrim<1>(suffix); - fileList.append(name); - if(1 || file::exists({path, filename, "unverified"}) == false) { - fileList.setImage(filenameList.size(), 0, {resource::game, sizeof resource::game}); - } else { - //disabled for now due to performance penalty - fileList.setImage(filenameList.size(), 0, {resource::unverified, sizeof resource::unverified}); - } - filenameList.append(filename); - } - } - - fileList.setSelection(selection); - fileList.setFocused(); - synchronize(); -} - -void Browser::fileListActivate() { - unsigned selection = fileList.selection(); - string filename = filenameList[selection]; - if(string{filename}.rtrim<1>("/").endswith(this->extension) == false) return setPath({path, filename}); - - outputFilename = {path, filename}; - onClose(); -} diff --git a/target-ethos/general/browser.hpp b/target-ethos/general/browser.hpp deleted file mode 100644 index c203d22a..00000000 --- a/target-ethos/general/browser.hpp +++ /dev/null @@ -1,37 +0,0 @@ -struct Browser : Window { - VerticalLayout layout; - HorizontalLayout pathLayout; - LineEdit pathEdit; - Button homeButton; - Button upButton; - ListView fileList; - HorizontalLayout controlLayout; - Label filterLabel; - Button openButton; - - string select(string title, string extension); - void saveConfiguration(); - void synchronize(); - void bootstrap(); - Browser(); - -private: - Configuration::Document config; - struct Folder { - string extension; - string path; - unsigned selection; - }; - vector folderList; - - string outputFilename; - - string extension; - string path; - lstring filenameList; - - void setPath(string path, unsigned selection = 0); - void fileListActivate(); -}; - -extern Browser* browser; diff --git a/target-ethos/general/dip-switches.cpp b/target-ethos/general/dip-switches.cpp index 900f624d..2734daaa 100644 --- a/target-ethos/general/dip-switches.cpp +++ b/target-ethos/general/dip-switches.cpp @@ -17,6 +17,7 @@ DipSwitches::DipSwitches() { controlLayout.append(accept, {80, 0}); setGeometry({128, 128, 250, layout.minimumSize().height}); + windowManager->append(this, "DipSwitches"); onClose = accept.onActivate = [&] { setModal(false); diff --git a/target-ethos/general/general.cpp b/target-ethos/general/general.cpp index 0d376dcf..d4cb3101 100644 --- a/target-ethos/general/general.cpp +++ b/target-ethos/general/general.cpp @@ -1,4 +1,4 @@ #include "../ethos.hpp" -#include "browser.cpp" +#include "library.cpp" #include "presentation.cpp" #include "dip-switches.cpp" diff --git a/target-ethos/general/general.hpp b/target-ethos/general/general.hpp index 04176b63..4e612af2 100644 --- a/target-ethos/general/general.hpp +++ b/target-ethos/general/general.hpp @@ -1,3 +1,3 @@ -#include "browser.hpp" +#include "library.hpp" #include "presentation.hpp" #include "dip-switches.hpp" diff --git a/target-ethos/general/library.cpp b/target-ethos/general/library.cpp new file mode 100644 index 00000000..5726cc01 --- /dev/null +++ b/target-ethos/general/library.cpp @@ -0,0 +1,185 @@ +LibraryManager* libraryManager = nullptr; + +LibraryBrowser::LibraryBrowser() { + setMargin(5); + + informationType.setText({ + "Title:\n", + "Revision:\n", + "Region:\n", + "Serial:" + }); + + append(folders, {~0, ~0}, 5); + append(informationLayout, {~0, Font::size(program->normalFont, " ").height * 4}); + informationLayout.append(informationType, {0, ~0}, 5); + informationLayout.append(information, {~0, ~0}); + + folders.onActivate = {&LibraryBrowser::onActivate, this}; + folders.onChange = {&LibraryBrowser::setInformation, this}; +} + +void LibraryBrowser::onActivate() { + if(folders.selected() == false) return; + unsigned selection = folders.selection(); + string pathname = {this->pathname, folders.text(selection, 0), filterSuffix}; + + if(libraryManager->slotLoad == false) { + libraryManager->setStatusText(folders.text(selection, 0)); + utility->loadMedia(pathname); + } else { + libraryManager->setStatusText({libraryManager->statusText(), " + ", folders.text(selection, 0)}); + libraryManager->setModal(false); + libraryManager->loadPathname = pathname; + } +} + +void LibraryBrowser::refresh() { + folders.reset(); + lstring pathnames = directory::ifolders(pathname, filterMask); + unsigned selection = 0; + for(auto& pathname : pathnames) { + folders.append(string{pathname}.rtrim<1>(filterSuffix)); + folders.setImage(selection++, 0, {resource::game, sizeof resource::game}); + } +} + +void LibraryBrowser::setFilter(const string& filter) { + this->filter = filter; + filterMask = {"*.", filter}; + filterSuffix = {".", filter, "/"}; +} + +void LibraryBrowser::setInformation() { + if(folders.selected() == false) { + information.setText(""); + } else { + string manifest = {pathname, folders.text(folders.selection(), 0), filterSuffix, "manifest.bml"}; + auto document = Markup::Document(file::read(manifest)); + information.setText({ + document["information/title"].text(), "\n", + document["information/revision"].text(), "\n", + document["information/region"].text(), "\n", + document["information/serial"].text(), "\n" + }); + } +} + +void LibraryBrowser::setPath(const string& pathname) { + this->pathname = pathname; + refresh(); +} + +LibraryManager::LibraryManager() { + setTitle("Game Library"); + setStatusVisible(); + setGeometry({128, 128, 960, 640}); + windowManager->append(this, "LibraryManager"); + + layout.setMargin(5); + libraryFrame.append("Import Games"); + importLayout.setMargin(5); + importInformation.setText({ + "higan manages games in a library. To play a game, you must first import the game.\n" + "After doing so, the game will appear inside your library, and can then be loaded and played." + }); + importButton.setText("Import Game ..."); + libraryFrame.setLayout(0, importLayout); + bootstrap(); + libraryFrame.setSelection(config->library.selection); + + append(layout); + layout.append(libraryFrame, {~0, ~0}); + importLayout.append(importInformation, {0, 0}, 5); + importLayout.append(importButton, {0, 0}); + + onClose = [&] { + setModal(false); + setVisible(false); + }; + + libraryFrame.onChange = [&] { + config->library.selection = libraryFrame.selection(); + }; + + importButton.onActivate = [&] { + if(program->ananke.open() == false) { + MessageWindow().setText("ananke must be installed to use this feature").warning(); + return; + } + function browse = program->ananke.sym("ananke_browse"); + if(!browse) return; + string pathname = browse(); + if(pathname.empty()) return; + MessageWindow().setText({"Successfully imported ", notdir(pathname.rtrim<1>("/"))}).information(); + string type = extension(pathname); + + unsigned selection = 1; + for(auto& browser : browsers) { + if(browser->filter == type) { + browser->refresh(); + libraryFrame.setSelection(selection); + break; + } + selection++; + } + }; +} + +void LibraryManager::bootstrap() { + unsigned selection = 1; + string basepath = utility->libraryPath(); + vector names; + + for(auto& emulator : program->emulator) { + for(auto& media : emulator->media) { + if(media.bootable == false) continue; + if(names.find(media.name)) continue; + names.append(media.name); + LibraryBrowser* browser = new LibraryBrowser; + browser->setFilter(media.type); + browser->setPath({basepath, media.name, "/"}); + libraryFrame.append(media.name); + libraryFrame.setLayout(selection++, *browser); + browsers.append(browser); + } + } + + for(auto& emulator : program->emulator) { + for(auto& media : emulator->media) { + if(media.bootable == true) continue; + if(names.find(media.name)) continue; + names.append(media.name); + LibraryBrowser* browser = new LibraryBrowser; + browser->setFilter(media.type); + browser->setPath({basepath, media.name, "/"}); + libraryFrame.append(media.name); + libraryFrame.setLayout(selection++, *browser); + browsers.append(browser); + } + } +} + +string LibraryManager::load(const string& type) { + setFocused(); + + unsigned selection = 1; + for(auto& browser : browsers) { + if(browser->filter == type) { + libraryFrame.setSelection(selection); + break; + } + selection++; + } + + slotLoad = true; + loadPathname = ""; + setModal(true); + slotLoad = false; + return loadPathname; +} + +void LibraryManager::setVisible(bool visible) { + setStatusText(""); + Window::setVisible(visible); +} diff --git a/target-ethos/general/library.hpp b/target-ethos/general/library.hpp new file mode 100644 index 00000000..e4a998f8 --- /dev/null +++ b/target-ethos/general/library.hpp @@ -0,0 +1,37 @@ +struct LibraryBrowser : VerticalLayout { + ListView folders; + HorizontalLayout informationLayout; + Label informationType; + Label information; + + LibraryBrowser(); + void onActivate(); + void refresh(); + void setFilter(const string& filter); + void setInformation(); + void setPath(const string& pathname); + + string filter; + string filterMask; + string filterSuffix; + string pathname; +}; + +struct LibraryManager : Window { + VerticalLayout layout; + TabFrame libraryFrame; + VerticalLayout importLayout; + Label importInformation; + Button importButton; + vector browsers; + + LibraryManager(); + void bootstrap(); + string load(const string& type); + void setVisible(bool visible = true); + + bool slotLoad = false; + string loadPathname; +}; + +extern LibraryManager* libraryManager; diff --git a/target-ethos/general/presentation.cpp b/target-ethos/general/presentation.cpp index e26f4e8c..6744b44a 100644 --- a/target-ethos/general/presentation.cpp +++ b/target-ethos/general/presentation.cpp @@ -62,7 +62,7 @@ Presentation::Presentation() { viewport.setDroppable(); loadMenu.setText("Library"); - loadImport.setText("Import Game ..."); + loadGame.setText("Load Game ..."); settingsMenu.setText("Settings"); videoMenu.setText("Video"); centerVideo.setText("Center"); @@ -90,11 +90,7 @@ Presentation::Presentation() { synchronizeTime.setText("Synchronize Time"); append(loadMenu); - for(auto& item : loadListSystem) loadMenu.append(*item); - if(program->ananke.open()) { - loadMenu.append(loadSeparator); - loadMenu.append(loadImport); - } + loadMenu.append(loadGame); for(auto& systemItem : emulatorList) append(systemItem->menu); append(settingsMenu); settingsMenu.append(videoMenu); @@ -154,15 +150,7 @@ Presentation::Presentation() { } }; - loadImport.onActivate = [&] { - if(program->ananke.open() == false) return; - function browse = program->ananke.sym("ananke_browse"); - if(!browse) return; - string pathname = browse(); - if(pathname.empty()) return; - utility->loadMedia(pathname); - }; - + loadGame.onActivate = [&] { libraryManager->setVisible(); }; shaderNone.onActivate = [&] { config->video.shader = "None"; utility->updateShader(); }; shaderBlur.onActivate = [&] { config->video.shader = "Blur"; utility->updateShader(); }; shaderEmulation.onActivate = [&] { config->video.shader = "Display Emulation"; utility->updateShader(); }; @@ -190,16 +178,6 @@ void Presentation::bootstrap() { auto iEmulator = new Emulator; iEmulator->interface = emulator; - for(auto& media : emulator->media) { - if(media.bootable == false) continue; - auto item = new Item; - item->onActivate = [=, &media] { - utility->loadMedia(iEmulator->interface, media); - }; - item->setText({media.name, " ..."}); - loadListSystem.append(item); - } - iEmulator->menu.setText(emulator->information.name); iEmulator->power.setText("Power"); iEmulator->reset.setText("Reset"); diff --git a/target-ethos/general/presentation.hpp b/target-ethos/general/presentation.hpp index 22fc809a..ff8b7850 100644 --- a/target-ethos/general/presentation.hpp +++ b/target-ethos/general/presentation.hpp @@ -22,9 +22,7 @@ struct Presentation : Window { Emulator* active = nullptr; Menu loadMenu; - vector loadListSystem; - Separator loadSeparator; - Item loadImport; + Item loadGame; Menu settingsMenu; Menu videoMenu; RadioItem centerVideo; diff --git a/target-ethos/settings/advanced.cpp b/target-ethos/settings/advanced.cpp index 947b8473..508c11a9 100644 --- a/target-ethos/settings/advanced.cpp +++ b/target-ethos/settings/advanced.cpp @@ -2,19 +2,19 @@ AdvancedSettings* advancedSettings = nullptr; AdvancedSettings::AdvancedSettings() { driverTitle.setFont(program->boldFont); - driverTitle.setText("Driver Selection"); + driverTitle.setText("Driver Selection:"); videoLabel.setText("Video:"); audioLabel.setText("Audio:"); inputLabel.setText("Input:"); libraryTitle.setFont(program->boldFont); - libraryTitle.setText("Game Library Path"); + libraryTitle.setText("Game Library:"); libraryLabel.setText("Path:"); libraryPath.setEditable(false); - string path = string::read({configpath(), "higan/library.bml"}).strip().ltrim<1>("Path: ").transform("\\", "/"); - if(path.empty()) path = {userpath(), "Emulation/"}; - if(path.endswith("/") == false) path.append("/"); - libraryPath.setText(path); + libraryPath.setText(utility->libraryPath()); libraryBrowse.setText("Browse ..."); + libraryShowOnStartup.setChecked(config->library.showOnStartup); + libraryShowOnStartup.setText("Show game library on program start"); + information.setText("Note: changing advanced settings requires program restart to take effect."); infoLabel.setFont(program->boldFont); infoLabel.setText({ Emulator::Name, " v", Emulator::Version, "\n", @@ -53,10 +53,12 @@ AdvancedSettings::AdvancedSettings() { driverLayout.append(inputLabel, {0, 0}, 5); driverLayout.append(inputDriver, {~0, 0}); append(libraryTitle, {~0, 0}); - append(libraryLayout, {~0, 0}, 15); + append(libraryLayout, {~0, 0}); libraryLayout.append(libraryLabel, {0, 0}, 5); libraryLayout.append(libraryPath, {~0, 0}, 5); libraryLayout.append(libraryBrowse, {80, 0}); + append(libraryShowOnStartup, {~0, 0}, 15); + append(information, {~0, 0}, 15); if(Intrinsics::platform() != Intrinsics::Platform::MacOSX) { append(spacer, {~0, ~0}); append(infoLabel, {~0, 0}); @@ -72,4 +74,8 @@ AdvancedSettings::AdvancedSettings() { file::write({configpath(), "higan/library.bml"}, {"Path: ", path, "\n"}); libraryPath.setText(path); }; + + libraryShowOnStartup.onToggle = [&] { + config->library.showOnStartup = libraryShowOnStartup.checked(); + }; } diff --git a/target-ethos/settings/advanced.hpp b/target-ethos/settings/advanced.hpp index 54059ba0..eaf0041a 100644 --- a/target-ethos/settings/advanced.hpp +++ b/target-ethos/settings/advanced.hpp @@ -13,6 +13,9 @@ struct AdvancedSettings : SettingsLayout { Label libraryLabel; LineEdit libraryPath; Button libraryBrowse; + CheckLabel libraryShowOnStartup; + + Label information; Widget spacer; Label infoLabel; diff --git a/target-ethos/utility/utility.cpp b/target-ethos/utility/utility.cpp index bf2cabec..2c449ea0 100644 --- a/target-ethos/utility/utility.cpp +++ b/target-ethos/utility/utility.cpp @@ -12,16 +12,6 @@ void Utility::loadMedia(string pathname) { pathname.transform("\\", "/"); if(pathname.endswith("/")) pathname.rtrim("/"); - //if a filename was provided: convert to game folder and then load - if(!directory::exists(pathname) && file::exists(pathname)) { - if(program->ananke.open() == false) return; - function open = program->ananke.sym("ananke_open"); - if(!open) return; - string name = open(pathname); - if(name.empty()) return; - return loadMedia(name); - } - if(!directory::exists(pathname)) return; string type = extension(pathname); @@ -30,20 +20,15 @@ void Utility::loadMedia(string pathname) { for(auto& media : emulator->media) { if(media.bootable == false) continue; if(type != media.type) continue; - return loadMedia(emulator, media, {pathname, "/"}); + loadMedia(emulator, media, {pathname, "/"}); + libraryManager->setVisible(false); + return; } } MessageWindow().setText("Unable to determine media type.").warning(); } -//load menu option selected -void Utility::loadMedia(Emulator::Interface* emulator, Emulator::Interface::Media& media) { - string pathname = browser->select({"Load ", media.name}, media.type); - if(!directory::exists(pathname)) return; - return loadMedia(emulator, media, pathname); -} - //load base cartridge void Utility::loadMedia(Emulator::Interface* emulator, Emulator::Interface::Media& media, string pathname) { unload(); @@ -62,7 +47,7 @@ void Utility::loadMedia(Emulator::Interface* emulator, Emulator::Interface::Medi //request from emulation core to load non-volatile media folder void Utility::loadRequest(unsigned id, string name, string type) { - string pathname = browser->select({"Load ", name}, type); + string pathname = libraryManager->load(type); //browser->select({"Load ", name}, type); if(pathname.empty()) return; path(id) = pathname; this->pathname.append(pathname); @@ -318,6 +303,13 @@ void Utility::showMessage(string message) { statusMessage = message; } +string Utility::libraryPath() { + string path = string::read({configpath(), "higan/library.bml"}).strip().ltrim<1>("Path: ").transform("\\", "/"); + if(path.empty()) path = {userpath(), "Emulation/"}; + if(path.endswith("/") == false) path.append("/"); + return path; +} + Utility::Utility() { tracerEnable = false; statusTime = 0; diff --git a/target-ethos/utility/utility.hpp b/target-ethos/utility/utility.hpp index 1ba64fc3..f433b867 100644 --- a/target-ethos/utility/utility.hpp +++ b/target-ethos/utility/utility.hpp @@ -2,7 +2,6 @@ struct Utility { void setInterface(Emulator::Interface* emulator); void loadMedia(string pathname); - void loadMedia(Emulator::Interface* emulator, Emulator::Interface::Media& media); void loadMedia(Emulator::Interface* emulator, Emulator::Interface::Media& media, string pathname); void loadRequest(unsigned id, string name, string type); @@ -30,6 +29,8 @@ struct Utility { void setStatusText(string text); void showMessage(string message); + string libraryPath(); + Utility(); lstring path;