Rename hiro::Property to hiro::Attribute
Disable XChaCha20 CSPRNG on Android for now due to compilation issues
Add macOS IOKit joypad support [Sintendo]
This commit is contained in:
byuu 2019-09-17 03:37:03 +09:00
parent 1e626e75ef
commit 18d2ab6435
32 changed files with 511 additions and 146 deletions

View File

@ -29,7 +29,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "bsnes";
static const string Version = "109.3";
static const string Version = "109.4";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org";

View File

@ -115,29 +115,29 @@ auto Presentation::create() -> void {
saveState.setIcon(Icon::Media::Record).setText("Save State");
for(uint index : range(QuickStates)) {
MenuItem item{&saveState};
item.setProperty("name", {"Quick/Slot ", 1 + index});
item.setProperty("title", {"Slot ", 1 + index});
item.setAttribute("name", {"Quick/Slot ", 1 + index});
item.setAttribute("title", {"Slot ", 1 + index});
item.setText({"Slot ", 1 + index});
item.onActivate([=] { program.saveState({"Quick/Slot ", 1 + index}); });
}
loadState.setIcon(Icon::Media::Rewind).setText("Load State");
for(uint index : range(QuickStates)) {
MenuItem item{&loadState};
item.setProperty("name", {"Quick/Slot ", 1 + index});
item.setProperty("title", {"Slot ", 1 + index});
item.setAttribute("name", {"Quick/Slot ", 1 + index});
item.setAttribute("title", {"Slot ", 1 + index});
item.setText({"Slot ", 1 + index});
item.onActivate([=] { program.loadState({"Quick/Slot ", 1 + index}); });
}
loadState.append(MenuSeparator());
loadState.append(MenuItem()
.setProperty("name", "Quick/Undo")
.setProperty("title", "Undo Last Save")
.setAttribute("name", "Quick/Undo")
.setAttribute("title", "Undo Last Save")
.setIcon(Icon::Edit::Undo).setText("Undo Last Save").onActivate([&] {
program.loadState("Quick/Undo");
}));
loadState.append(MenuItem()
.setProperty("name", "Quick/Redo")
.setProperty("title", "Redo Last Undo")
.setAttribute("name", "Quick/Redo")
.setAttribute("title", "Redo Last Undo")
.setIcon(Icon::Edit::Redo).setText("Redo Last Undo").onActivate([&] {
program.loadState("Quick/Redo");
}));
@ -149,11 +149,11 @@ auto Presentation::create() -> void {
}
}));
speedMenu.setIcon(Icon::Device::Clock).setText("Speed").setEnabled(!settings.video.blocking && settings.audio.blocking);
speedSlowest.setText("50% (Slowest)").setProperty("multiplier", "2.0").onActivate([&] { program.updateAudioFrequency(); });
speedSlow.setText("75% (Slow)").setProperty("multiplier", "1.333").onActivate([&] { program.updateAudioFrequency(); });
speedNormal.setText("100% (Normal)").setProperty("multiplier", "1.0").onActivate([&] { program.updateAudioFrequency(); });
speedFast.setText("150% (Fast)").setProperty("multiplier", "0.667").onActivate([&] { program.updateAudioFrequency(); });
speedFastest.setText("200% (Fastest)").setProperty("multiplier", "0.5").onActivate([&] { program.updateAudioFrequency(); });
speedSlowest.setText("50% (Slowest)").setAttribute("multiplier", "2.0").onActivate([&] { program.updateAudioFrequency(); });
speedSlow.setText("75% (Slow)").setAttribute("multiplier", "1.333").onActivate([&] { program.updateAudioFrequency(); });
speedNormal.setText("100% (Normal)").setAttribute("multiplier", "1.0").onActivate([&] { program.updateAudioFrequency(); });
speedFast.setText("150% (Fast)").setAttribute("multiplier", "0.667").onActivate([&] { program.updateAudioFrequency(); });
speedFastest.setText("200% (Fastest)").setAttribute("multiplier", "0.5").onActivate([&] { program.updateAudioFrequency(); });
runMenu.setIcon(Icon::Media::Play).setText("Run Mode");
runEmulation.setText("Normal").onActivate([&] {
});
@ -335,7 +335,7 @@ auto Presentation::updateDeviceMenu() -> void {
if(port.name == "Expansion Port" && device.name == "21fx") continue;
MenuRadioItem item{menu};
item.setProperty("deviceID", device.id);
item.setAttribute("deviceID", device.id);
item.setText(tr(device.name));
item.onActivate([=] {
settings(path).setValue(device.name);
@ -363,7 +363,7 @@ auto Presentation::updateDeviceSelections() -> void {
auto deviceID = emulator->connected(port.id);
for(auto& action : menu->actions()) {
if(auto item = action.cast<MenuRadioItem>()) {
if(item.property("deviceID").natural() == deviceID) {
if(item.attribute("deviceID").natural() == deviceID) {
item.setChecked();
break;
}
@ -385,7 +385,7 @@ auto Presentation::updateSizeMenu() -> void {
uint multipliers = max(1, height / 240);
for(uint multiplier : range(1, multipliers + 1)) {
MenuRadioItem item{&sizeMenu};
item.setProperty("multiplier", multiplier);
item.setAttribute("multiplier", multiplier);
item.setText({multiplier, "x (", 240 * multiplier, "p)"});
item.onActivate([=] {
settings.video.multiplier = multiplier;
@ -394,7 +394,7 @@ auto Presentation::updateSizeMenu() -> void {
sizeGroup.append(item);
}
for(auto item : sizeGroup.objects<MenuRadioItem>()) {
if(settings.video.multiplier == item.property("multiplier").natural()) {
if(settings.video.multiplier == item.attribute("multiplier").natural()) {
item.setChecked();
}
}
@ -413,11 +413,11 @@ auto Presentation::updateStateMenus() -> void {
for(auto& action : saveState.actions()) {
if(auto item = action.cast<MenuItem>()) {
if(auto name = item.property("name")) {
if(auto name = item.attribute("name")) {
if(auto offset = states.find([&](auto& state) { return state.name == name; })) {
item.setText({item.property("title"), " (", chrono::local::datetime(states[*offset].date), ")"});
item.setText({item.attribute("title"), " (", chrono::local::datetime(states[*offset].date), ")"});
} else {
item.setText({item.property("title"), " (empty)"});
item.setText({item.attribute("title"), " (empty)"});
}
}
}
@ -425,13 +425,13 @@ auto Presentation::updateStateMenus() -> void {
for(auto& action : loadState.actions()) {
if(auto item = action.cast<MenuItem>()) {
if(auto name = item.property("name")) {
if(auto name = item.attribute("name")) {
if(auto offset = states.find([&](auto& state) { return state.name == name; })) {
item.setEnabled(true);
item.setText({item.property("title"), " (", chrono::local::datetime(states[*offset].date), ")"});
item.setText({item.attribute("title"), " (", chrono::local::datetime(states[*offset].date), ")"});
} else {
item.setEnabled(false);
item.setText({item.property("title"), " (empty)"});
item.setText({item.attribute("title"), " (empty)"});
}
}
}

View File

@ -56,7 +56,7 @@ auto Program::updateAudioFrequency() -> void {
double frequency = settings.audio.frequency + settings.audio.skew;
if(!settings.video.blocking && settings.audio.blocking) {
for(auto item : presentation.speedGroup.objects<MenuRadioItem>()) {
if(item.checked()) frequency *= item.property("multiplier").real();
if(item.checked()) frequency *= item.attribute("multiplier").real();
}
}
Emulator::audio.setFrequency(frequency);

View File

@ -54,19 +54,19 @@ auto EnhancementSettings::create() -> void {
mode7Label.setText("HD Mode 7 (fast PPU only)").setFont(Font().setBold());
mode7ScaleLabel.setText("Scale:");
mode7Scale.append(ComboButtonItem().setText( "240p").setProperty("multiplier", 1));
mode7Scale.append(ComboButtonItem().setText( "480p").setProperty("multiplier", 2));
mode7Scale.append(ComboButtonItem().setText( "720p").setProperty("multiplier", 3));
mode7Scale.append(ComboButtonItem().setText( "960p").setProperty("multiplier", 4));
mode7Scale.append(ComboButtonItem().setText("1200p").setProperty("multiplier", 5));
mode7Scale.append(ComboButtonItem().setText("1440p").setProperty("multiplier", 6));
mode7Scale.append(ComboButtonItem().setText("1680p").setProperty("multiplier", 7));
mode7Scale.append(ComboButtonItem().setText("1920p").setProperty("multiplier", 8));
mode7Scale.append(ComboButtonItem().setText( "240p").setAttribute("multiplier", 1));
mode7Scale.append(ComboButtonItem().setText( "480p").setAttribute("multiplier", 2));
mode7Scale.append(ComboButtonItem().setText( "720p").setAttribute("multiplier", 3));
mode7Scale.append(ComboButtonItem().setText( "960p").setAttribute("multiplier", 4));
mode7Scale.append(ComboButtonItem().setText("1200p").setAttribute("multiplier", 5));
mode7Scale.append(ComboButtonItem().setText("1440p").setAttribute("multiplier", 6));
mode7Scale.append(ComboButtonItem().setText("1680p").setAttribute("multiplier", 7));
mode7Scale.append(ComboButtonItem().setText("1920p").setAttribute("multiplier", 8));
for(uint n = 1; n <= 8; n++) {
if(settings.emulator.hack.ppu.mode7.scale == n) mode7Scale.item(n - 1).setSelected();
}
mode7Scale.onChange([&] {
settings.emulator.hack.ppu.mode7.scale = mode7Scale.selected().property("multiplier").natural();
settings.emulator.hack.ppu.mode7.scale = mode7Scale.selected().attribute("multiplier").natural();
emulator->configure("Hacks/PPU/Mode7/Scale", settings.emulator.hack.ppu.mode7.scale);
});
mode7Perspective.setText("Perspective correction").setChecked(settings.emulator.hack.ppu.mode7.perspective).onToggle([&] {

View File

@ -87,7 +87,7 @@ auto InputSettings::activePort() -> InputPort& {
}
auto InputSettings::activeDevice() -> InputDevice& {
auto index = deviceList.selected().property("index").natural();
auto index = deviceList.selected().attribute("index").natural();
return activePort().devices[index];
}
@ -105,7 +105,7 @@ auto InputSettings::reloadDevices() -> void {
uint index = 0;
for(auto& device : activePort().devices) {
if(device.mappings) { //only display devices that have configurable inputs
deviceList.append(ComboButtonItem().setText(device.name).setProperty("index", index));
deviceList.append(ComboButtonItem().setText(device.name).setAttribute("index", index));
}
index++;
}

View File

@ -38,7 +38,7 @@ auto CheatDatabase::findCheats() -> void {
cheatList.append(ListViewItem()
.setCheckable()
.setText(cheat["description"].text())
.setProperty("code", code)
.setAttribute("code", code)
);
}
@ -53,7 +53,7 @@ auto CheatDatabase::findCheats() -> void {
auto CheatDatabase::addCheats() -> void {
for(auto item : cheatList.items()) {
if(item.checked()) {
cheatEditor.addCheat({item.text(), item.property("code"), false});
cheatEditor.addCheat({item.text(), item.attribute("code"), false});
}
}
setVisible(false);

View File

@ -25,7 +25,7 @@ auto ManifestViewer::loadManifest() -> void {
auto titles = emulator->titles();
for(uint offset : range(manifests.size())) {
ComboButtonItem item{&manifestOption};
item.setProperty("manifest", manifests[offset]);
item.setAttribute("manifest", manifests[offset]);
item.setText(titles[offset]);
bool verified = false;
if(offset == 0) verified = program.superFamicom.verified;
@ -41,7 +41,7 @@ auto ManifestViewer::loadManifest() -> void {
auto ManifestViewer::selectManifest() -> void {
auto selected = manifestOption.selected();
uint offset = selected->offset();
manifestView.setText(selected.property("manifest"));
manifestView.setText(selected.attribute("manifest"));
string location;
if(offset == 0) location = program.superFamicom.location;
if(offset == 1 && program.gameBoy) location = program.gameBoy.location;

View File

@ -13,31 +13,31 @@ auto StateWindow::create() -> void {
}
auto StateWindow::show(string name) -> void {
setProperty("type", {name.split("/").first(), "/"});
setProperty("name", {name.split("/").last()});
nameValue.setText(property("name"));
setAttribute("type", {name.split("/").first(), "/"});
setAttribute("name", {name.split("/").last()});
nameValue.setText(attribute("name"));
doChange();
setTitle(!property("name") ? "Add State" : "Rename State");
setTitle(!attribute("name") ? "Add State" : "Rename State");
setAlignment(*toolsWindow);
setVisible();
setFocused();
nameValue.setFocused();
acceptButton.setText(!property("name") ? "Add" : "Rename");
acceptButton.setText(!attribute("name") ? "Add" : "Rename");
}
auto StateWindow::doChange() -> void {
auto name = nameValue.text().strip();
bool valid = name && !name.contains("\\\"\t/:*?<>|");
if(property("name") && name != property("name")) {
if(attribute("name") && name != attribute("name")) {
//don't allow a state to be renamed to the same name as an existing state (as this would overwrite it)
if(program.hasState({property("type"), name})) valid = false;
if(program.hasState({attribute("type"), name})) valid = false;
}
nameValue.setBackgroundColor(valid ? Color{} : Color{255, 224, 224});
acceptButton.setEnabled(valid);
}
auto StateWindow::doAccept() -> void {
string name = {property("type"), nameValue.text().strip()};
string name = {attribute("type"), nameValue.text().strip()};
if(acceptButton.text() == "Add") {
stateManager.createState(name);
} else {
@ -64,23 +64,23 @@ auto StateManager::create() -> void {
stateList.resizeColumns();
});
categoryLabel.setText("Category:");
categoryOption.append(ComboButtonItem().setText("Managed States").setProperty("type", "Managed/"));
categoryOption.append(ComboButtonItem().setText("Quick States").setProperty("type", "Quick/"));
categoryOption.append(ComboButtonItem().setText("Managed States").setAttribute("type", "Managed/"));
categoryOption.append(ComboButtonItem().setText("Quick States").setAttribute("type", "Quick/"));
categoryOption.onChange([&] { loadStates(); });
statePreviewSeparator1.setColor({192, 192, 192});
statePreviewLabel.setFont(Font().setBold()).setText("Preview");
statePreviewSeparator2.setColor({192, 192, 192});
loadButton.setText("Load").onActivate([&] {
if(auto item = stateList.selected()) program.loadState(item.property("name"));
if(auto item = stateList.selected()) program.loadState(item.attribute("name"));
});
saveButton.setText("Save").onActivate([&] {
if(auto item = stateList.selected()) program.saveState(item.property("name"));
if(auto item = stateList.selected()) program.saveState(item.attribute("name"));
});
addButton.setText("Add").onActivate([&] {
stateWindow.show(type());
});
editButton.setText("Rename").onActivate([&] {
if(auto item = stateList.selected()) stateWindow.show(item.property("name"));
if(auto item = stateList.selected()) stateWindow.show(item.attribute("name"));
});
removeButton.setText("Remove").onActivate([&] {
removeStates();
@ -88,7 +88,7 @@ auto StateManager::create() -> void {
}
auto StateManager::type() const -> string {
return categoryOption.selected().property("type");
return categoryOption.selected().attribute("type");
}
auto StateManager::loadStates() -> void {
@ -98,7 +98,7 @@ auto StateManager::loadStates() -> void {
auto type = this->type();
for(auto& state : program.availableStates(type)) {
TableViewItem item{&stateList};
item.setProperty("name", state.name);
item.setAttribute("name", state.name);
item.append(TableViewCell().setText(state.name.trimLeft(type, 1L)));
item.append(TableViewCell().setText(chrono::local::datetime(state.date)));
}
@ -111,19 +111,19 @@ auto StateManager::createState(string name) -> void {
}
program.saveState(name);
for(auto& item : stateList.items()) {
item.setSelected(item.property("name") == name);
item.setSelected(item.attribute("name") == name);
}
stateList.doChange();
}
auto StateManager::modifyState(string name) -> void {
if(auto item = stateList.selected()) {
string from = item.property("name");
string from = item.attribute("name");
string to = name;
if(from != to) {
program.renameState(from, to);
for(auto& item : stateList.items()) {
item.setSelected(item.property("name") == to);
item.setSelected(item.attribute("name") == to);
}
stateList.doChange();
}
@ -135,7 +135,7 @@ auto StateManager::removeStates() -> void {
if(MessageDialog("Are you sure you want to permanently remove the selected state(s)?")
.setAlignment(*toolsWindow).question() == "Yes") {
auto lock = acquire();
for(auto& item : batched) program.removeState(item.property("name"));
for(auto& item : batched) program.removeState(item.attribute("name"));
loadStates();
}
}
@ -153,7 +153,7 @@ auto StateManager::updateSelection() -> void {
statePreview.setColor({0, 0, 0});
if(batched.size() == 1) {
if(auto saveState = program.loadStateData(batched.first().property("name"))) {
if(auto saveState = program.loadStateData(batched.first().attribute("name"))) {
if(saveState.size() >= 3 * sizeof(uint)) {
uint signature = memory::readl<sizeof(uint)>(saveState.data() + 0 * sizeof(uint));
uint serializer = memory::readl<sizeof(uint)>(saveState.data() + 1 * sizeof(uint));
@ -182,10 +182,10 @@ auto StateManager::updateSelection() -> void {
auto StateManager::stateEvent(string name) -> void {
if(locked() || !name.beginsWith(type())) return;
auto selected = stateList.selected().property("name");
auto selected = stateList.selected().attribute("name");
loadStates();
for(auto& item : stateList.items()) {
item.setSelected(item.property("name") == selected);
item.setSelected(item.attribute("name") == selected);
}
stateList.doChange();
}

View File

@ -27,7 +27,7 @@
#define Hiro_BrowserWindow
#define Hiro_MessageWindow
#define Hiro_Property
#define Hiro_Attribute
#define Hiro_Object
#define Hiro_Group

29
hiro/core/attribute.cpp Normal file
View File

@ -0,0 +1,29 @@
#if defined(Hiro_Attribute)
Attribute::Attribute(const string& name, const any& value) {
state.name = name;
state.value = value;
}
auto Attribute::operator==(const Attribute& source) const -> bool {
return state.name == source.state.name;
}
auto Attribute::operator<(const Attribute& source) const -> bool {
return state.name < source.state.name;
}
auto Attribute::name() const -> string {
return state.name;
}
auto Attribute::setValue(const any& value) -> type& {
state.value = value;
return *this;
}
auto Attribute::value() const -> any& {
return state.value;
}
#endif

20
hiro/core/attribute.hpp Normal file
View File

@ -0,0 +1,20 @@
#if defined(Hiro_Attribute)
struct Attribute {
using type = Attribute;
Attribute(const string& name, const any& value = {});
auto operator==(const Attribute& source) const -> bool;
auto operator< (const Attribute& source) const -> bool;
auto name() const -> string;
auto setValue(const any& value = {}) -> type&;
auto value() const -> any&;
private:
struct State {
string name;
mutable any value;
} state;
};
#endif

View File

@ -48,7 +48,7 @@ namespace hiro {
#include "browser-window.cpp"
#include "message-window.cpp"
#include "property.cpp"
#include "attribute.cpp"
#include "object.cpp"
#include "group.cpp"

View File

@ -352,6 +352,7 @@ struct Keyboard {
#if defined(Hiro_Mouse)
struct Mouse {
enum class Button : uint { Left, Middle, Right };
enum class Click : uint { Single, Double };
Mouse() = delete;
@ -410,7 +411,7 @@ struct MessageWindow {
};
#endif
#include "property.hpp"
#include "attribute.hpp"
#define Declare(Name) \
using type = m##Name; \

View File

@ -43,45 +43,43 @@ struct mObject {
virtual auto setVisible(bool visible = true) -> type&;
auto visible(bool recursive = false) const -> bool;
template<typename T = string> auto property(const string& name) const -> T {
if(auto property = state.properties.find(name)) {
if(property->value().is<T>()) return property->value().get<T>();
template<typename T = string> auto attribute(const string& name) const -> T {
if(auto attribute = state.attributes.find(name)) {
if(attribute->value().is<T>()) return attribute->value().get<T>();
}
return {};
}
//this template basically disables implicit template type deduction:
//if setProperty(name, value) is called without a type, the type will be a string, so property(name) will just work.
//if setProperty<T>(name, value) is called, the type will be T. as such, U must be cast to T on assignment.
//if setAttribute(name, value) is called without a type, the type will be a string, so attribute(name) will just work.
//if setAttribute<T>(name, value) is called, the type will be T. as such, U must be cast to T on assignment.
//when T = string, value must be convertible to a string.
//U defaults to a string, so that setProperty(name, {values, ...}) will deduce U as a string.
template<typename T = string, typename U = string> auto setProperty(const string& name, const U& value) -> type& {
//U defaults to a string, so that setAttribute(name, {values, ...}) will deduce U as a string.
template<typename T = string, typename U = string> auto setAttribute(const string& name, const U& value) -> type& {
if constexpr(std::is_same_v<T, string> && !std::is_same_v<U, string>) {
return setProperty(name, string{value});
return setAttribute(name, string{value});
}
if(auto property = state.properties.find(name)) {
if((const T&)value) property->setValue((const T&)value);
else state.properties.remove(*property);
if(auto attribute = state.attributes.find(name)) {
if((const T&)value) attribute->setValue((const T&)value);
else state.attributes.remove(*attribute);
} else {
if((const T&)value) state.properties.insert({name, (const T&)value});
if((const T&)value) state.attributes.insert({name, (const T&)value});
}
return *this;
}
//private:
//sizeof(mObject) == 88
struct State {
Font font; //32
set<Property> properties; //16
mObject* parent = nullptr; // 8
int offset = -1; // 4
char enabled = true; // 1+
char visible = true; // 1=4
set<Attribute> attributes;
Font font;
mObject* parent = nullptr;
int offset = -1;
char enabled = true;
char visible = true;
} state;
wObject instance; // 8
pObject* delegate = nullptr; // 8
//vtable // 8
wObject instance;
pObject* delegate = nullptr;
virtual auto construct() -> void;
virtual auto destruct() -> void;

View File

@ -1,29 +0,0 @@
#if defined(Hiro_Property)
Property::Property(const string& name, const any& value) {
state.name = name;
state.value = value;
}
auto Property::operator==(const Property& source) const -> bool {
return state.name == source.state.name;
}
auto Property::operator<(const Property& source) const -> bool {
return state.name < source.state.name;
}
auto Property::name() const -> string {
return state.name;
}
auto Property::setValue(const any& value) -> type& {
state.value = value;
return *this;
}
auto Property::value() const -> any& {
return state.value;
}
#endif

View File

@ -1,20 +0,0 @@
#if defined(Hiro_Property)
struct Property {
using type = Property;
Property(const string& name, const any& value = {});
auto operator==(const Property& source) const -> bool;
auto operator< (const Property& source) const -> bool;
auto name() const -> string;
auto setValue(const any& value = {}) -> type&;
auto value() const -> any&;
private:
struct State {
string name;
mutable any value;
} state;
};
#endif

View File

@ -28,6 +28,7 @@
} \
return T(); \
} \
template<typename T = string> auto attribute(const string& name) const { return self().attribute<T>(name); } \
auto enabled(bool recursive = false) const { return self().enabled(recursive); } \
auto focused() const { return self().focused(); } \
auto font(bool recursive = false) const { return self().font(recursive); } \
@ -38,12 +39,11 @@
} \
return Object(); \
} \
template<typename T = string> auto property(const string& name) const { return self().property<T>(name); } \
auto remove() { return self().remove(), *this; } \
template<typename T = string, typename U = string> auto setAttribute(const string& name, const U& value = {}) { return self().setAttribute<T, U>(name, value), *this; } \
auto setEnabled(bool enabled = true) { return self().setEnabled(enabled), *this; } \
auto setFocused() { return self().setFocused(), *this; } \
auto setFont(const Font& font = {}) { return self().setFont(font), *this; } \
template<typename T = string, typename U = string> auto setProperty(const string& name, const U& value = {}) { return self().setProperty<T, U>(name, value), *this; } \
auto setVisible(bool visible = true) { return self().setVisible(visible), *this; } \
auto visible(bool recursive = false) const { return self().visible(recursive); } \
@ -794,6 +794,7 @@ struct TreeViewItem : sTreeViewItem {
struct TreeView : sTreeView {
DeclareSharedWidget(TreeView)
auto activation() const { return self().activation(); }
auto append(sTreeViewItem item) { return self().append(item), *this; }
auto backgroundColor() const { return self().backgroundColor(); }
auto collapse(bool recursive = true) { return self().collapse(recursive), *this; }
@ -814,6 +815,7 @@ struct TreeView : sTreeView {
auto reset() { return self().reset(), *this; }
auto selectNone() { return self().selectNone(), *this; }
auto selected() const { return self().selected(); }
auto setActivation(Mouse::Click activation = Mouse::Click::Double) { return self().setActivation(activation), *this; }
auto setBackgroundColor(Color color = {}) { return self().setBackgroundColor(color), *this; }
auto setForegroundColor(Color color = {}) { return self().setForegroundColor(color), *this; }
};
@ -936,6 +938,7 @@ struct Window : sWindow {
auto setFrameSize(Size size) { return self().setFrameSize(size), *this; }
auto setFullScreen(bool fullScreen = true) { return self().setFullScreen(fullScreen), *this; }
auto setGeometry(Geometry geometry) { return self().setGeometry(geometry), *this; }
auto setGeometry(Alignment alignment, Size size) { return self().setGeometry(alignment, size), *this; }
auto setMaximized(bool maximized) { return self().setMaximized(maximized), *this; }
auto setMaximumSize(Size size = {}) { return self().setMaximumSize(size), *this; }
auto setMinimized(bool minimized) { return self().setMinimized(minimized), *this; }

View File

@ -11,6 +11,10 @@ auto mTreeView::destruct() -> void {
//
auto mTreeView::activation() const -> Mouse::Click {
return state.activation;
}
auto mTreeView::append(sTreeViewItem item) -> type& {
state.items.append(item);
item->setParent(this, itemCount() - 1);
@ -119,6 +123,12 @@ auto mTreeView::selected() const -> TreeViewItem {
return item(state.selectedPath);
}
auto mTreeView::setActivation(Mouse::Click activation) -> type& {
state.activation = activation;
signal(setActivation, activation);
return *this;
}
auto mTreeView::setBackgroundColor(Color color) -> type& {
state.backgroundColor = color;
signal(setBackgroundColor, color);

View File

@ -3,6 +3,7 @@ struct mTreeView : mWidget {
Declare(TreeView)
using mObject::remove;
auto activation() const -> Mouse::Click;
auto append(sTreeViewItem item) -> type&;
auto backgroundColor() const -> Color;
auto collapse(bool recursive = true) -> type&;
@ -23,12 +24,14 @@ struct mTreeView : mWidget {
auto reset() -> type&;
auto selectNone() -> type&;
auto selected() const -> TreeViewItem;
auto setActivation(Mouse::Click activation = Mouse::Click::Double) -> type&;
auto setBackgroundColor(Color color = {}) -> type&;
auto setForegroundColor(Color color = {}) -> type&;
auto setParent(mObject* parent = nullptr, int offset = -1) -> type&;
//private:
struct State {
Mouse::Click activation = Mouse::Click::Double;
Color backgroundColor;
Color foregroundColor;
vector<sTreeViewItem> items;

View File

@ -279,6 +279,17 @@ auto mWindow::setGeometry(Geometry geometry) -> type& {
return *this;
}
auto mWindow::setGeometry(Alignment alignment, Size size) -> type& {
auto margin = signal(frameMargin);
auto width = margin.width() + size.width();
auto height = margin.height() + size.height();
auto workspace = Monitor::workspace();
auto x = workspace.x() + alignment.horizontal() * (workspace.width() - width);
auto y = workspace.y() + alignment.vertical() * (workspace.height() - height);
setFrameGeometry({(int)x, (int)y, (int)width, (int)height});
return *this;
}
auto mWindow::setMaximized(bool maximized) -> type& {
state.maximized = maximized;
signal(setMaximized, maximized);

View File

@ -49,6 +49,7 @@ struct mWindow : mObject {
auto setFrameSize(Size size) -> type&;
auto setFullScreen(bool fullScreen = true) -> type&;
auto setGeometry(Geometry geometry) -> type&;
auto setGeometry(Alignment, Size) -> type&;
auto setMaximized(bool maximized = true) -> type&;
auto setMaximumSize(Size size = {}) -> type&;
auto setMinimized(bool minimized = true) -> type&;

View File

@ -359,7 +359,7 @@ auto BrowserDialogWindow::run() -> BrowserDialog::Response {
window.setAlignment(state.relativeTo, state.alignment);
window.setDismissable();
window.setVisible();
fileName.setFocused();
fileName.setText(state.name).setFocused().doChange();
Application::processEvents();
view->resizeColumns();
window.setModal();
@ -480,6 +480,11 @@ auto BrowserDialog::setFilters(const vector<string>& filters) -> type& {
return *this;
}
auto BrowserDialog::setName(const string& name) -> type& {
state.name = name;
return *this;
}
auto BrowserDialog::setOptions(const vector<string>& options) -> type& {
state.options = options;
return *this;

View File

@ -17,6 +17,7 @@ struct BrowserDialog {
auto setAlignment(Alignment = Alignment::Center) -> type&;
auto setAlignment(sWindow relativeTo, Alignment = Alignment::Center) -> type&;
auto setFilters(const vector<string>& filters = {}) -> type&;
auto setName(const string& name = "") -> type&;
auto setOptions(const vector<string>& options = {}) -> type&;
auto setPath(const string& path = "") -> type&;
auto setTitle(const string& title = "") -> type&;
@ -26,6 +27,7 @@ private:
string action;
Alignment alignment = Alignment::Center;
vector<string> filters = {"*"};
string name;
vector<string> options;
string path;
sWindow relativeTo;

View File

@ -200,13 +200,13 @@ auto pTableView::_cellWidth(unsigned _row, unsigned _column) -> unsigned {
}
auto pTableView::_columnWidth(unsigned _column) -> unsigned {
unsigned width = 8;
unsigned width = 6;
if(auto column = self().column(_column)) {
if(auto& icon = column->state.icon) {
width += icon.width() + 2;
}
if(auto& text = column->state.text) {
width += pFont::size(column->font(true), text).width();
width += pFont::size(column->font(true), text).width() + 8;
}
if(column->state.sorting != Sort::None) {
width += 20;

View File

@ -12,6 +12,7 @@ static auto TreeView_buttonEvent(GtkTreeView*, GdkEventButton* gdkEvent, pTreeVi
static auto TreeView_change(GtkTreeSelection*, pTreeView* p) -> void { p->_updateSelected(); }
static auto TreeView_context(GtkTreeView*, pTreeView* p) -> void { p->self().doContext(); }
static auto TreeView_dataFunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeModel* model, GtkTreeIter* iter, pTreeView* p) -> void { return p->_doDataFunc(column, renderer, iter); }
static auto TreeView_keyPress(GtkWidget*, GdkEventKey*, pTreeView* p) -> int { p->suppressActivate = false; return false; }
static auto TreeView_toggle(GtkCellRendererToggle*, char* path, pTreeView* p) -> void { p->_togglePath(path); }
auto pTreeView::construct() -> void {
@ -52,11 +53,13 @@ auto pTreeView::construct() -> void {
gtk_tree_view_append_column(gtkTreeView, gtkTreeViewColumn);
gtk_tree_view_set_search_column(gtkTreeView, 2);
setActivation(state().activation);
setBackgroundColor(state().backgroundColor);
setForegroundColor(state().foregroundColor);
g_signal_connect(G_OBJECT(gtkWidgetChild), "button-press-event", G_CALLBACK(TreeView_buttonEvent), (gpointer)this);
g_signal_connect(G_OBJECT(gtkWidgetChild), "button-release-event", G_CALLBACK(TreeView_buttonEvent), (gpointer)this);
g_signal_connect(G_OBJECT(gtkWidgetChild), "key-press-event", G_CALLBACK(TreeView_keyPress), (gpointer)this);
g_signal_connect(G_OBJECT(gtkWidgetChild), "popup-menu", G_CALLBACK(TreeView_context), (gpointer)this);
g_signal_connect(G_OBJECT(gtkWidgetChild), "row-activated", G_CALLBACK(TreeView_activate), (gpointer)this);
g_signal_connect(G_OBJECT(gtkTreeSelection), "changed", G_CALLBACK(TreeView_change), (gpointer)this);
@ -87,6 +90,10 @@ auto pTreeView::append(sTreeViewItem item) -> void {
auto pTreeView::remove(sTreeViewItem item) -> void {
}
auto pTreeView::setActivation(Mouse::Click activation) -> void {
//handled by callbacks
}
auto pTreeView::setBackgroundColor(Color color) -> void {
auto gdkColor = CreateColor(color);
gtk_widget_modify_base(gtkWidgetChild, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
@ -115,6 +122,11 @@ auto pTreeView::setGeometry(Geometry geometry) -> void {
//
auto pTreeView::_activatePath(GtkTreePath* gtkPath) -> void {
if(suppressActivate) {
suppressActivate = false;
return;
}
char* path = gtk_tree_path_to_string(gtkPath);
if(auto item = self().item(string{path}.transform(":", "/"))) {
if(!locked()) self().doActivate();
@ -140,6 +152,25 @@ auto pTreeView::_buttonEvent(GdkEventButton* gdkEvent) -> signed {
}
}
if(gdkEvent->button == 1) {
//emulate activate-on-single-click, which is only available in GTK+ 3.8 and later
if(gtkPath && self().activation() == Mouse::Click::Single) {
//selectedPath must be updated for TreeView::doActivate() to act on the correct TreeViewItem.
//as this will then cause "changed" to not see that the path has changed, we must handle that case here as well.
char* path = gtk_tree_path_to_string(gtkPath);
string selectedPath = string{path}.transform(":", "/");
g_free(path);
if(state().selectedPath != selectedPath) {
state().selectedPath = selectedPath;
self().doChange();
}
self().doActivate();
//"row-activated" is sent before "button-press-event" (GDK_2BUTTON_PRESS);
//so stop a double-click from calling TreeView::doActivate() twice by setting a flag after single-clicks
suppressActivate = true; //key presses will clear this flag to allow key-activations to work correctly
}
}
if(gdkEvent->button == 3) {
//multi-selection mode: (not implemented in TreeView yet ... but code is here anyway for future use)
//if multiple items are selected, and one item is right-clicked on (for a context menu), GTK clears selection on all other items

View File

@ -7,6 +7,7 @@ struct pTreeView : pWidget {
auto append(sTreeViewItem item) -> void;
auto remove(sTreeViewItem item) -> void;
auto setActivation(Mouse::Click activation) -> void;
auto setBackgroundColor(Color color) -> void;
auto setFocused() -> void override;
auto setForegroundColor(Color color) -> void;
@ -32,6 +33,7 @@ struct pTreeView : pWidget {
GtkCellRenderer* gtkCellPixbuf = nullptr;
GtkCellRenderer* gtkCellText = nullptr;
GtkEntry* gtkEntry = nullptr;
bool suppressActivate = false;
bool suppressChange = false;
};

View File

@ -317,13 +317,15 @@ auto pWindow::setFullScreen(bool fullScreen) -> void {
} else {
gtk_window_unfullscreen(GTK_WINDOW(widget));
}
auto time = chrono::millisecond();
while(chrono::millisecond() - time < 20) Application::processEvents();
while(chrono::millisecond() - time < 20) {
Application::processEvents();
}
}
auto pWindow::setGeometry(Geometry geometry) -> void {
auto margin = frameMargin();
gtk_window_move(GTK_WINDOW(widget), geometry.x() - margin.x(), geometry.y() - margin.y());
setMaximumSize(state().maximumSize);
setMinimumSize(state().minimumSize);
@ -333,10 +335,13 @@ auto pWindow::setGeometry(Geometry geometry) -> void {
Application::processEvents();
}
gtk_window_move(GTK_WINDOW(widget), geometry.x() - margin.x(), geometry.y() - margin.y());
gtk_window_resize(GTK_WINDOW(widget), geometry.width(), geometry.height() + _menuHeight() + _statusHeight());
auto time2 = chrono::millisecond();
while(chrono::millisecond() - time2 < 20) Application::processEvents();
while(chrono::millisecond() - time2 < 20) {
Application::processEvents();
}
}
auto pWindow::setMaximized(bool maximized) -> void {

View File

@ -44,7 +44,13 @@ auto pComboButton::reset() -> void {
SendMessage(hwnd, CB_RESETCONTENT, 0, 0);
}
//Windows overrides the height parameter for a ComboButton's SetWindowPos to be the drop-down list height.
//the canonical way to set the actual height is through CB_SETITEMHEIGHT. However, doing so is bugged.
//the ComboButton will end up not being painted for ~500ms after calling ShowWindow(hwnd, SW_NORMAL) on it.
//thus, implementing windows that use multiple pages of controls via toggling visibility will flicker heavily.
//as a result, the best we can do is center the actual widget within the requested space.
auto pComboButton::setGeometry(Geometry geometry) -> void {
//since the ComboButton has a fixed height, it will always be the same, even before calling setGeometry() once.
RECT rc;
GetWindowRect(hwnd, &rc);
geometry.setY(geometry.y() + (geometry.height() - (rc.bottom - rc.top)));

View File

@ -5,7 +5,9 @@
#include <nall/range.hpp>
#include <nall/serializer.hpp>
#include <nall/stdint.hpp>
#if !defined(PLATFORM_ANDROID)
#include <nall/cipher/chacha20.hpp>
#endif
#if defined(PLATFORM_LINUX) && __has_include(<sys/random.h>)
#include <sys/random.h>
@ -125,6 +127,7 @@ private:
}
#if !defined(PLATFORM_ANDROID)
namespace CSPRNG {
//XChaCha20 cryptographically secure pseudo-random number generator
@ -153,6 +156,7 @@ private:
};
}
#endif
//

View File

@ -6,7 +6,7 @@ ifeq ($(ruby),)
else ifeq ($(platform),macos)
ruby += video.cgl
ruby += audio.openal
ruby += input.quartz input.carbon
ruby += input.quartz #input.carbon
else ifeq ($(platform),linux)
ruby += video.glx video.glx2 video.xvideo video.xshm
ruby += audio.oss audio.alsa audio.openal audio.pulseaudio audio.pulseaudiosimple audio.ao

278
ruby/input/joypad/iokit.cpp Normal file
View File

@ -0,0 +1,278 @@
#pragma once
#include <IOKit/hid/IOHIDLib.h>
auto deviceMatchingCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device) -> void;
auto deviceRemovalCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device) -> void;
struct InputJoypadIOKit {
Input& input;
InputJoypadIOKit(Input& input) : input(input) {}
struct Joypad {
auto appendElements(CFArrayRef elements) -> void {
for(uint n : range(CFArrayGetCount(elements))) {
IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, n);
IOHIDElementType type = IOHIDElementGetType(element);
uint32_t usage = IOHIDElementGetUsage(element);
switch(type) {
case kIOHIDElementTypeInput_Button:
appendButton(element);
break;
case kIOHIDElementTypeInput_Axis:
case kIOHIDElementTypeInput_Misc:
if(usage == kHIDUsage_Sim_Accelerator || usage == kHIDUsage_Sim_Brake
|| usage == kHIDUsage_Sim_Rudder || usage == kHIDUsage_Sim_Throttle
|| usage == kHIDUsage_GD_X || usage == kHIDUsage_GD_Y || usage == kHIDUsage_GD_Z
|| usage == kHIDUsage_GD_Rx || usage == kHIDUsage_GD_Ry || usage == kHIDUsage_GD_Rz
|| usage == kHIDUsage_GD_Slider || usage == kHIDUsage_GD_Dial || usage == kHIDUsage_GD_Wheel
) appendAxis(element);
if(usage == kHIDUsage_GD_DPadUp || usage == kHIDUsage_GD_DPadDown
|| usage == kHIDUsage_GD_DPadLeft || usage == kHIDUsage_GD_DPadRight
|| usage == kHIDUsage_GD_Start || usage == kHIDUsage_GD_Select
|| usage == kHIDUsage_GD_SystemMainMenu
) appendButton(element);
if(usage == kHIDUsage_GD_Hatswitch
) appendHat(element);
break;
case kIOHIDElementTypeCollection:
if(CFArrayRef children = IOHIDElementGetChildren(element)) appendElements(children);
break;
}
}
}
auto appendAxis(IOHIDElementRef element) -> void {
IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
if(auto duplicate = axes.find([cookie](auto axis) { return IOHIDElementGetCookie(axis) == cookie; })) {
return;
}
int min = IOHIDElementGetLogicalMin(element);
int max = IOHIDElementGetLogicalMax(element);
int range = max - min;
if(range == 0) return;
hid->axes().append(axes.size());
axes.append(element);
}
auto appendHat(IOHIDElementRef element) -> void {
IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
if(auto duplicate = hats.find([cookie](auto hat) { return IOHIDElementGetCookie(hat) == cookie; })) {
return;
}
uint n = hats.size() * 2;
hid->hats().append(n + 0);
hid->hats().append(n + 1);
hats.append(element);
}
auto appendButton(IOHIDElementRef element) -> void {
IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
if(auto duplicate = buttons.find([cookie](auto button) { return IOHIDElementGetCookie(button) == cookie; })) {
return;
}
hid->buttons().append(buttons.size());
buttons.append(element);
}
shared_pointer<HID::Joypad> hid{new HID::Joypad};
IOHIDDeviceRef device = nullptr;
vector<IOHIDElementRef> axes;
vector<IOHIDElementRef> hats;
vector<IOHIDElementRef> buttons;
};
vector<Joypad> joypads;
IOHIDManagerRef manager = nullptr;
enum : int { Center = 0, Up = -1, Down = +1, Left = -1, Right = +1 };
auto assign(shared_pointer<HID::Joypad> hid, uint groupID, uint inputID, int16_t value) -> void {
auto& group = hid->group(groupID);
if(group.input(inputID).value() == value) return;
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
group.input(inputID).setValue(value);
}
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
detectDevices(); //hotplug support
for(auto& jp : joypads) {
IOHIDDeviceRef device = jp.device;
for(uint n : range(jp.axes.size())) {
int value = 0;
IOHIDValueRef valueRef;
if(IOHIDDeviceGetValue(device, jp.axes[n], &valueRef) == kIOReturnSuccess) {
int min = IOHIDElementGetLogicalMin(jp.axes[n]);
int max = IOHIDElementGetLogicalMax(jp.axes[n]);
int range = max - min;
value = (IOHIDValueGetIntegerValue(valueRef) - min) * 65535LL / range - 32767;
}
assign(jp.hid, HID::Joypad::GroupID::Axis, n, sclamp<16>(value));
}
for(uint n : range(jp.hats.size())) {
int x = Center;
int y = Center;
IOHIDValueRef valueRef;
if(IOHIDDeviceGetValue(device, jp.hats[n], &valueRef) == kIOReturnSuccess) {
int position = IOHIDValueGetIntegerValue(valueRef);
int min = IOHIDElementGetLogicalMin(jp.hats[n]);
int max = IOHIDElementGetLogicalMax(jp.hats[n]);
if(position >= min && position <= max) {
position -= min;
int range = max - min + 1;
if(range == 4) {
position *= 2;
}
if(range == 8) {
switch(position) {
case 0: x = Up; y = Center; break;
case 1: x = Up; y = Right; break;
case 2: x = Center; y = Right; break;
case 3: x = Down; y = Right; break;
case 4: x = Down; y = Center; break;
case 5: x = Down; y = Left; break;
case 6: x = Center; y = Left; break;
case 7: x = Up; y = Left; break;
}
}
}
}
assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 0, x * 32767);
assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 1, x * 32767);
}
for(uint n : range(jp.buttons.size())) {
int value = 0;
IOHIDValueRef valueRef;
if(IOHIDDeviceGetValue(device, jp.buttons[n], &valueRef) == kIOReturnSuccess) {
value = IOHIDValueGetIntegerValue(valueRef);
}
assign(jp.hid, HID::Joypad::GroupID::Button, n, (bool)value);
}
devices.append(jp.hid);
}
}
auto rumble(uint64_t id, bool enable) -> bool {
//todo
return false;
}
auto initialize() -> bool {
manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
if(!manager) return false;
CFArrayRef matcher = createMatcher();
if(!matcher) {
releaseManager();
return false;
}
IOHIDManagerSetDeviceMatchingMultiple(manager, matcher);
IOHIDManagerRegisterDeviceMatchingCallback(manager, deviceMatchingCallback, this);
IOHIDManagerRegisterDeviceRemovalCallback(manager, deviceRemovalCallback, this);
if(IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
releaseManager();
return false;
}
detectDevices();
return true;
}
auto terminate() -> void {
if(!manager) return;
IOHIDManagerClose(manager, kIOHIDOptionsTypeNone);
releaseManager();
}
auto appendJoypad(IOHIDDeviceRef device) -> void {
Joypad jp;
jp.device = device;
int32_t vendorID, productID;
CFNumberGetValue((CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)), kCFNumberSInt32Type, &vendorID);
CFNumberGetValue((CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)), kCFNumberSInt32Type, &productID);
jp.hid->setVendorID(vendorID);
jp.hid->setProductID(productID);
CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, kIOHIDOptionsTypeNone);
if(elements) {
jp.appendElements(elements);
CFRelease(elements);
joypads.append(jp);
}
}
auto removeJoypad(IOHIDDeviceRef device) -> void {
for(uint n : range(joypads.size())) {
if(joypads[n].device == device) {
joypads.remove(n);
return;
}
}
}
private:
auto releaseManager() -> void {
CFRelease(manager);
manager = nullptr;
}
auto createMatcher() -> CFArrayRef {
CFDictionaryRef dict1 = createMatcherCriteria(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
if(!dict1) return nullptr;
CFDictionaryRef dict2 = createMatcherCriteria(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
if(!dict2) return CFRelease(dict1), nullptr;
CFDictionaryRef dict3 = createMatcherCriteria(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController);
if(!dict3) return CFRelease(dict1), CFRelease(dict2), nullptr;
const void* values[] = {dict1, dict2, dict3};
CFArrayRef array = CFArrayCreate(kCFAllocatorDefault, values, 3, &kCFTypeArrayCallBacks);
CFRelease(dict1), CFRelease(dict2), CFRelease(dict3);
return array;
}
auto createMatcherCriteria(uint32_t page, uint32_t usage) -> CFDictionaryRef {
CFNumberRef pageNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
if(!pageNumber) return nullptr;
CFNumberRef usageNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
if(!usageNumber) return nullptr;
const void* keys[] = {CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey)};
const void* values[] = {pageNumber, usageNumber};
CFDictionaryRef dict = CFDictionaryCreate(
kCFAllocatorDefault, keys, values, 2,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks
);
CFRelease(pageNumber), CFRelease(usageNumber);
return dict;
}
auto detectDevices() -> void {
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFStringRef runLoopMode = CFSTR("rubyJoypadIOKit");
IOHIDManagerScheduleWithRunLoop(manager, runLoop, runLoopMode);
while(CFRunLoopRunInMode(runLoopMode, 0, true) == kCFRunLoopRunHandledSource);
IOHIDManagerUnscheduleFromRunLoop(manager, runLoop, runLoopMode);
}
};
auto deviceMatchingCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device) -> void {
((InputJoypadIOKit*)context)->appendJoypad(device);
}
auto deviceRemovalCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device) -> void {
((InputJoypadIOKit*)context)->removeJoypad(device);
}

View File

@ -1,8 +1,9 @@
#include "keyboard/quartz.cpp"
#include "joypad/iokit.cpp"
struct InputQuartz : InputDriver {
InputQuartz& self = *this;
InputQuartz(Input& super) : InputDriver(super), keyboard(super) {}
InputQuartz(Input& super) : InputDriver(super), keyboard(super), joypad(super) {}
~InputQuartz() { terminate(); }
auto create() -> bool override {
@ -19,6 +20,7 @@ struct InputQuartz : InputDriver {
auto poll() -> vector<shared_pointer<HID::Device>> override {
vector<shared_pointer<HID::Device>> devices;
keyboard.poll(devices);
joypad.poll(devices);
return devices;
}
@ -30,14 +32,17 @@ private:
auto initialize() -> bool {
terminate();
if(!keyboard.initialize()) return false;
if(!joypad.initialize()) return false;
return isReady = true;
}
auto terminate() -> void {
isReady = false;
keyboard.terminate();
joypad.terminate();
}
bool isReady = false;
InputKeyboardQuartz keyboard;
InputJoypadIOKit joypad;
};