Update to v094r42 release.

byuu says:

I imagine you guys will like this WIP very much.

Changelog:
- ListView check boxes on Windows
- ListView removal of columns on reset (changing input dropdowns)
- DirectSound audio duplication on latency change
- DirectSound crash on 20ms latency
- Fullscreen window sizing in multi-monitor setups
- Allow joypad bindings of hotkeys
- Allow triggers to be mapped (Xbox 360 / XInput / Windows only)
- Support joypad rumble for Game Boy Player
- Video scale settings modified from {1x,2x,3x} to {2x,3x,4x}
- System menu now renames to active emulation core
- Added fast forward hotkey

Not changing for v095:
- not adding input focus settings yet
- not adding shaders yet

Not changing at all:
- not implementing maximize
This commit is contained in:
Tim Allen 2015-08-24 19:42:11 +10:00
parent 7081f46e45
commit c45633550e
21 changed files with 136 additions and 75 deletions

View File

@ -8,7 +8,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "094.41";
static const string Version = "094.42";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -219,8 +219,10 @@ auto mWindow::setFrameSize(Size size) -> type& {
}
auto mWindow::setFullScreen(bool fullScreen) -> type& {
state.fullScreen = fullScreen;
signal(setFullScreen, fullScreen);
if(fullScreen != state.fullScreen) {
state.fullScreen = fullScreen;
signal(setFullScreen, fullScreen);
}
return *this;
}

View File

@ -65,8 +65,7 @@ static auto Window_configure(GtkWidget* widget, GdkEvent* event, pWindow* p) ->
//move
if(geometry.x() != p->state().geometry.x() || geometry.y() != p->state().geometry.y()) {
if(!p->state().fullScreen) {
p->state().geometry.setX(geometry.x());
p->state().geometry.setY(geometry.y());
p->state().geometry.setPosition({geometry.x(), geometry.y()});
}
if(!p->locked()) p->self().doMove();
}
@ -106,8 +105,9 @@ static auto Window_sizeAllocate(GtkWidget* widget, GtkAllocation* allocation, pW
if(allocation->width == p->lastAllocation.width
&& allocation->height == p->lastAllocation.height) return;
p->state().geometry.setWidth(allocation->width);
p->state().geometry.setHeight(allocation->height);
if(!p->state().fullScreen) {
p->state().geometry.setSize({allocation->width, allocation->height});
}
if(auto& layout = p->state().layout) {
layout->setGeometry(p->self().geometry().setPosition(0, 0));
@ -259,10 +259,13 @@ auto pWindow::setFocused() -> void {
}
auto pWindow::setFullScreen(bool fullScreen) -> void {
if(fullScreen == false) {
gtk_window_unfullscreen(GTK_WINDOW(widget));
} else {
if(fullScreen) {
windowedGeometry = state().geometry;
gtk_window_fullscreen(GTK_WINDOW(widget));
state().geometry = Monitor::geometry(Monitor::primary());
} else {
gtk_window_unfullscreen(GTK_WINDOW(widget));
state().geometry = windowedGeometry;
}
}

View File

@ -5,15 +5,6 @@ namespace hiro {
struct pWindow : pObject {
Declare(Window, Object)
GtkWidget* widget = nullptr;
GtkWidget* menuContainer = nullptr;
GtkWidget* formContainer = nullptr;
GtkWidget* statusContainer = nullptr;
GtkWidget* gtkMenu = nullptr;
GtkWidget* gtkStatus = nullptr;
GtkAllocation lastAllocation = {0};
bool onSizePending = false;
auto append(sLayout layout) -> void;
auto append(sMenuBar menuBar) -> void;
auto append(sStatusBar statusBar) -> void;
@ -45,6 +36,16 @@ struct pWindow : pObject {
auto _setStatusText(const string& text) -> void;
auto _setStatusVisible(bool visible) -> void;
auto _statusHeight() const -> signed;
GtkWidget* widget = nullptr;
GtkWidget* menuContainer = nullptr;
GtkWidget* formContainer = nullptr;
GtkWidget* statusContainer = nullptr;
GtkWidget* gtkMenu = nullptr;
GtkWidget* gtkStatus = nullptr;
GtkAllocation lastAllocation = {0};
bool onSizePending = false;
Geometry windowedGeometry{128, 128, 256, 256};
};
}

View File

@ -89,13 +89,6 @@ auto pWindow::remove(sMenuBar menuBar) -> void {
auto pWindow::remove(sStatusBar statusBar) -> void {
}
/*
//orphan() destroys and recreates widgets (to disassociate them from their parent);
//attempting to create widget again after QApplication::quit() crashes libQtGui
if(qtApplication) widget.p.orphan();
}
*/
auto pWindow::setBackgroundColor(Color color) -> void {
if(color) {
QPalette palette;
@ -120,15 +113,20 @@ auto pWindow::setFocused() -> void {
}
auto pWindow::setFullScreen(bool fullScreen) -> void {
if(!fullScreen) {
setResizable(state().resizable);
qtWindow->showNormal();
qtWindow->adjustSize();
} else {
lock();
if(fullScreen) {
windowedGeometry = state().geometry;
qtLayout->setSizeConstraint(QLayout::SetDefaultConstraint);
qtContainer->setFixedSize(Desktop::size().width() - frameMargin().width(), Desktop::size().height() - frameMargin().height());
qtWindow->showFullScreen();
state().geometry = Monitor::geometry(Monitor::primary());
} else {
setResizable(state().resizable);
qtWindow->showNormal();
qtWindow->adjustSize();
self().setGeometry(windowedGeometry);
}
unlock();
}
auto pWindow::setGeometry(Geometry geometry) -> void {
@ -256,8 +254,10 @@ auto QtWindow::closeEvent(QCloseEvent* event) -> void {
auto QtWindow::moveEvent(QMoveEvent* event) -> void {
if(!p.locked() && !p.state().fullScreen && p.qtWindow->isVisible()) {
p.state().geometry.setX(p.state().geometry.x() + event->pos().x() - event->oldPos().x());
p.state().geometry.setY(p.state().geometry.y() + event->pos().y() - event->oldPos().y());
p.state().geometry.setPosition({
p.state().geometry.x() + event->pos().x() - event->oldPos().x(),
p.state().geometry.y() + event->pos().y() - event->oldPos().y()
});
}
if(!p.locked()) {
@ -287,8 +287,10 @@ auto QtWindow::keyReleaseEvent(QKeyEvent* event) -> void {
auto QtWindow::resizeEvent(QResizeEvent*) -> void {
if(!p.locked() && !p.state().fullScreen && p.qtWindow->isVisible()) {
p.state().geometry.setWidth(p.qtContainer->geometry().width());
p.state().geometry.setHeight(p.qtContainer->geometry().height());
p.state().geometry.setSize({
p.qtContainer->geometry().width(),
p.qtContainer->geometry().height()
});
}
if(auto& layout = p.state().layout) {

View File

@ -34,6 +34,7 @@ struct pWindow : pObject {
QMenuBar* qtMenuBar = nullptr;
QStatusBar* qtStatusBar = nullptr;
QWidget* qtContainer = nullptr;
Geometry windowedGeometry{128, 128, 256, 256};
};
}

View File

@ -6,7 +6,7 @@ static const unsigned WindowsVista = 0x0600;
static const unsigned Windows7 = 0x0601;
static auto OsVersion() -> unsigned {
OSVERSIONINFO versionInfo = {0};
OSVERSIONINFO versionInfo{0};
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&versionInfo);
return (versionInfo.dwMajorVersion << 8) + (versionInfo.dwMajorVersion << 0);
@ -283,6 +283,20 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms
}
case WM_NOTIFY: {
//WC_TABCONTROL requires parenting widgets to it; rather than the WINDOW
//without doing this; backgrounds on transparent controls (eg STATIC) are painted wrong
//this causes some WC_LISTVIEW WM_NOTIFY messages to only go to the WC_TABCONTROL WNDPROC
//all other controls also send their messages to the WC_TABCONTROL WNDPROC
//to avoid duplicating all message logic, hiro shares a WNDPROC with Window and TabFrame
//LVN_ITEM(ACTIVATE,CHANGED) are only sent to the TabFrame, as expected
//yet for unknown reasons, LVN_COLUMNCLICK and NM_(CLICK,DBLCLK,RCLICK) are
//sent to both the TabFrame, and then again to the Window's WNDPROC
//this causes on(Sort,Toggle,Context) to trigger callbacks twice
//if we try to block propagation to the Window (via return instead of break); then
//this will result in the LVN_ITEM(ACTIVATE,CHANGED) never being invoked (unsure why)
//as a workaround; we must detect these message to the Windows' WNDPROC, and block them
bool isWindowCallback = (object == window);
auto header = (LPNMHDR)lparam;
auto object = (mObject*)GetWindowLongPtr((HWND)header->hwndFrom, GWLP_USERDATA);
if(!object) break;
@ -298,15 +312,16 @@ static auto CALLBACK Shared_windowProc(WindowProc windowProc, HWND hwnd, UINT ms
break;
}
if(header->code == LVN_COLUMNCLICK) {
listView->self()->onSort(lparam);
if(!isWindowCallback) listView->self()->onSort(lparam);
break;
}
if(header->code == NM_CLICK || header->code == NM_DBLCLK) {
listView->self()->onToggle(lparam);
//onToggle performs the test to ensure the ListViewItem clicked was checkable
if(!isWindowCallback) listView->self()->onToggle(lparam);
break;
}
if(header->code == NM_RCLICK) {
listView->self()->onContext(lparam);
if(!isWindowCallback) listView->self()->onContext(lparam);
break;
}
if(header->code == NM_CUSTOMDRAW) {

View File

@ -52,6 +52,10 @@ auto pListView::append(sListViewItem item) -> void {
}
auto pListView::remove(sListViewHeader header) -> void {
LVCOLUMN lvColumn{LVCF_WIDTH};
while(ListView_GetColumn(hwnd, 0, &lvColumn)) {
ListView_DeleteColumn(hwnd, 0);
}
}
auto pListView::remove(sListViewItem item) -> void {

View File

@ -91,10 +91,8 @@ auto pWindow::setFont(const string& font) -> void {
auto pWindow::setFullScreen(bool fullScreen) -> void {
auto style = GetWindowLongPtr(hwnd, GWL_STYLE) & WS_VISIBLE;
lock();
if(fullScreen == false) {
SetWindowLongPtr(hwnd, GWL_STYLE, style | (state().resizable ? ResizableStyle : FixedStyle));
setGeometry(state().geometry);
} else {
if(fullScreen) {
windowedGeometry = self().geometry();
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
MONITORINFOEX info;
memset(&info, 0, sizeof(MONITORINFOEX));
@ -104,10 +102,13 @@ auto pWindow::setFullScreen(bool fullScreen) -> void {
Geometry geometry = {rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top};
SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_POPUP);
Geometry margin = frameMargin();
setGeometry({
self().setGeometry({
geometry.x() + margin.x(), geometry.y() + margin.y(),
geometry.width() - margin.width(), geometry.height() - margin.height()
});
} else {
SetWindowLongPtr(hwnd, GWL_STYLE, style | (state().resizable ? ResizableStyle : FixedStyle));
self().setGeometry(windowedGeometry);
}
unlock();
}
@ -116,7 +117,7 @@ auto pWindow::setGeometry(Geometry geometry) -> void {
lock();
Geometry margin = frameMargin();
SetWindowPos(
hwnd, NULL,
hwnd, nullptr,
geometry.x() - margin.x(), geometry.y() - margin.y(),
geometry.width() + margin.width(), geometry.height() + margin.height(),
SWP_NOZORDER | SWP_FRAMECHANGED

View File

@ -42,6 +42,7 @@ struct pWindow : pObject {
HFONT hstatusfont = nullptr;
HBRUSH hbrush = nullptr;
COLORREF hbrushColor = 0;
Geometry windowedGeometry{128, 128, 256, 256};
};
}

View File

@ -63,7 +63,8 @@ struct AudioDS : Audio {
}
if(name == Audio::Latency && value.is<unsigned>()) {
settings.latency = value.get<unsigned>();
//latency settings below 40ms causes DirectSound to hang
settings.latency = max(40u, value.get<unsigned>());
if(ds) init();
return true;
}

View File

@ -13,7 +13,7 @@ struct ConfigurationManager : Configuration::Document {
struct Video : Configuration::Node {
string driver;
bool synchronize = false;
string scale = "Normal";
string scale = "Small";
bool aspectCorrection = true;
string filter = "Blur";
bool colorEmulation = true;

View File

@ -1,7 +1,7 @@
auto InputManager::appendHotkeys() -> void {
{ auto hotkey = new InputHotkey;
hotkey->name = "Toggle Fullscreen";
hotkey->action = [] {
hotkey->press = [] {
presentation->toggleFullScreen();
};
hotkeys.append(hotkey);
@ -9,7 +9,7 @@ auto InputManager::appendHotkeys() -> void {
{ auto hotkey = new InputHotkey;
hotkey->name = "Toggle Mouse Capture";
hotkey->action = [] {
hotkey->press = [] {
input->acquired() ? input->release() : input->acquire();
};
hotkeys.append(hotkey);
@ -17,7 +17,7 @@ auto InputManager::appendHotkeys() -> void {
{ auto hotkey = new InputHotkey;
hotkey->name = "Save State";
hotkey->action = [] {
hotkey->press = [] {
program->saveState(0);
};
hotkeys.append(hotkey);
@ -25,7 +25,7 @@ auto InputManager::appendHotkeys() -> void {
{ auto hotkey = new InputHotkey;
hotkey->name = "Load State";
hotkey->action = [] {
hotkey->press = [] {
program->loadState(0);
};
hotkeys.append(hotkey);
@ -33,15 +33,28 @@ auto InputManager::appendHotkeys() -> void {
{ auto hotkey = new InputHotkey;
hotkey->name = "Pause Emulation";
hotkey->action = [] {
hotkey->press = [] {
program->pause = !program->pause;
};
hotkeys.append(hotkey);
}
{ auto hotkey = new InputHotkey;
hotkey->name = "Fast Forward";
hotkey->press = [] {
video->set(Video::Synchronize, false);
audio->set(Audio::Synchronize, false);
};
hotkey->release = [] {
video->set(Video::Synchronize, ::config->video.synchronize);
audio->set(Audio::Synchronize, ::config->audio.synchronize);
};
hotkeys.append(hotkey);
}
{ auto hotkey = new InputHotkey;
hotkey->name = "Power Cycle";
hotkey->action = [] {
hotkey->press = [] {
program->powerCycle();
};
hotkeys.append(hotkey);
@ -49,7 +62,7 @@ auto InputManager::appendHotkeys() -> void {
{ auto hotkey = new InputHotkey;
hotkey->name = "Soft Reset";
hotkey->action = [] {
hotkey->press = [] {
program->softReset();
};
hotkeys.append(hotkey);
@ -65,7 +78,8 @@ auto InputManager::appendHotkeys() -> void {
auto InputManager::pollHotkeys() -> void {
for(auto& hotkey : hotkeys) {
int16 state = hotkey->poll();
if(hotkey->state == 0 && state == 1 && hotkey->action) hotkey->action();
if(hotkey->state == 0 && state == 1 && hotkey->press) hotkey->press();
if(hotkey->state == 1 && state == 0 && hotkey->release) hotkey->release();
hotkey->state = state;
}
}

View File

@ -42,8 +42,9 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, unsigned group, unsi
}
if((device->isJoypad() && group == HID::Joypad::GroupID::Axis)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat)) {
if(newValue < -16384) {
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) {
if(newValue < -16384 && group != HID::Joypad::GroupID::Trigger) { //triggers are always hi
this->assignment = {encoding, "/Lo"};
return bind(), true;
}
@ -69,7 +70,7 @@ auto InputMapping::bind(shared_pointer<HID::Device> device, unsigned group, unsi
if(isRumble()) {
if(device->isJoypad() && group == HID::Joypad::GroupID::Button) {
if(newValue) {
encoding = {this->assignment, "/Rumble"};
this->assignment = {encoding, "/Rumble"};
return bind(), true;
}
}
@ -87,7 +88,8 @@ auto InputMapping::poll() -> int16 {
if(device->isMouse() && group == HID::Mouse::GroupID::Button) return value != 0;
if(device->isJoypad() && group == HID::Joypad::GroupID::Button) return value != 0;
if((device->isJoypad() && group == HID::Joypad::GroupID::Axis)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat)) {
|| (device->isJoypad() && group == HID::Joypad::GroupID::Hat)
|| (device->isJoypad() && group == HID::Joypad::GroupID::Trigger)) {
if(qualifier == Qualifier::Lo) return value < -16384;
if(qualifier == Qualifier::Hi) return value > +16384;
}
@ -102,6 +104,11 @@ auto InputMapping::poll() -> int16 {
return 0;
}
auto InputMapping::rumble(bool enable) -> void {
if(!device) return;
::input->rumble(device->id(), enable);
}
auto InputMapping::unbind() -> void {
this->assignment = "None";
this->device = nullptr;

View File

@ -2,6 +2,7 @@ struct InputMapping {
auto bind() -> void;
auto bind(shared_pointer<HID::Device> device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> bool;
auto poll() -> int16;
auto rumble(bool enable) -> void;
auto unbind() -> void;
auto isDigital() const -> bool { return !link || link->type == 0; }
@ -21,7 +22,8 @@ struct InputMapping {
};
struct InputHotkey : InputMapping {
function<void ()> action;
function<void ()> press;
function<void ()> release;
int16 state = 0;
};

View File

@ -32,14 +32,14 @@ Presentation::Presentation() {
settingsMenu.setText("Settings");
videoScaleMenu.setText("Video Scale");
if(config->video.scale == "Small") videoScaleSmall.setChecked();
if(config->video.scale == "Normal") videoScaleNormal.setChecked();
if(config->video.scale == "Medium") videoScaleMedium.setChecked();
if(config->video.scale == "Large") videoScaleLarge.setChecked();
videoScaleSmall.setText("Small").onActivate([&] {
config->video.scale = "Small";
resizeViewport();
});
videoScaleNormal.setText("Normal").onActivate([&] {
config->video.scale = "Normal";
videoScaleMedium.setText("Medium").onActivate([&] {
config->video.scale = "Medium";
resizeViewport();
});
videoScaleLarge.setText("Large").onActivate([&] {
@ -137,8 +137,8 @@ auto Presentation::updateEmulator() -> void {
auto Presentation::resizeViewport() -> void {
signed scale = 1;
if(config->video.scale == "Small" ) scale = 1;
if(config->video.scale == "Normal") scale = 2;
if(config->video.scale == "Small" ) scale = 2;
if(config->video.scale == "Medium") scale = 3;
if(config->video.scale == "Large" ) scale = 4;
signed width = 256;
@ -163,24 +163,25 @@ auto Presentation::resizeViewport() -> void {
setSize({windowWidth, windowHeight});
viewport.setGeometry({(windowWidth - width) / 2, (windowHeight - height) / 2, width, height});
} else {
auto desktop = Desktop::size();
signed windowWidth = geometry().width();
signed windowHeight = geometry().height();
//aspect ratio correction is always enabled in fullscreen mode
//note that below algorithm yields 7:6 ratio on 2560x(1440,1600) monitors
//this is extremely close to the optimum 8:7 ratio
//it is used so that linear interpolation isn't required
//todo: we should handle other resolutions nicely as well
unsigned multiplier = desktop.height() / height;
unsigned multiplier = windowHeight / height;
width *= 1 + multiplier;
height *= multiplier;
signed x = (desktop.width() - width) / 2;
signed y = (desktop.height() - height) / 2;
signed x = (windowWidth - width) / 2;
signed y = (windowHeight - height) / 2;
if(x < 0) x = 0;
if(y < 0) y = 0;
if(width > desktop.width()) width = desktop.width();
if(height > desktop.height()) height = desktop.height();
if(width > windowWidth) width = windowWidth;
if(height > windowHeight) height = windowHeight;
viewport.setGeometry({x, y, width, height});
}

View File

@ -19,9 +19,9 @@ struct Presentation : Window {
Menu settingsMenu{&menuBar};
Menu videoScaleMenu{&settingsMenu};
MenuRadioItem videoScaleSmall{&videoScaleMenu};
MenuRadioItem videoScaleNormal{&videoScaleMenu};
MenuRadioItem videoScaleMedium{&videoScaleMenu};
MenuRadioItem videoScaleLarge{&videoScaleMenu};
Group videoScales{&videoScaleSmall, &videoScaleNormal, &videoScaleLarge};
Group videoScales{&videoScaleSmall, &videoScaleMedium, &videoScaleLarge};
MenuSeparator videoScaleSeparator{&videoScaleMenu};
MenuCheckItem aspectCorrection{&videoScaleMenu};
Menu videoFilterMenu{&settingsMenu};

View File

@ -146,6 +146,11 @@ auto Program::inputPoll(unsigned port, unsigned device, unsigned input) -> int16
}
auto Program::inputRumble(unsigned port, unsigned device, unsigned input, bool enable) -> void {
if(presentation->focused() || !enable) {
auto guid = emulator->port[port].device[device].input[input].guid;
auto mapping = (InputMapping*)guid;
if(mapping) return mapping->rumble(enable);
}
}
auto Program::dipSettings(const Markup::Node& node) -> unsigned {

View File

@ -28,7 +28,7 @@ auto Program::loadMedia(Emulator::Interface& emulator_, Emulator::Interface::Med
presentation->resizeViewport();
presentation->setTitle(emulator->title());
presentation->systemMenu.setVisible(true);
presentation->systemMenu.setText(media.name).setVisible(true);
presentation->toolsMenu.setVisible(true);
presentation->updateEmulator();
toolsManager->cheatEditor.loadCheats();

View File

@ -50,6 +50,7 @@ auto Program::updateVideoPalette() -> void {
auto Program::updateAudio() -> void {
if(!audio) return;
audio->clear();
audio->set(Audio::Frequency, config->audio.frequency);
audio->set(Audio::Latency, config->audio.latency);
if(auto resampler = config->audio.resampler) {

View File

@ -63,7 +63,7 @@ auto HotkeySettings::assignMapping() -> void {
auto HotkeySettings::inputEvent(shared_pointer<HID::Device> device, unsigned group, unsigned input, int16 oldValue, int16 newValue) -> void {
if(!activeMapping) return;
if(!device->isKeyboard() || oldValue != 0 || newValue != 1) return;
if(device->isMouse()) return;
if(activeMapping->bind(device, group, input, oldValue, newValue)) {
activeMapping = nullptr;