mirror of
https://github.com/libretro/bsnes-libretro.git
synced 2024-12-03 15:20:52 +00:00
v109.4
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:
parent
1e626e75ef
commit
18d2ab6435
@ -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";
|
||||
|
@ -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)"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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([&] {
|
||||
|
@ -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++;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
29
hiro/core/attribute.cpp
Normal 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
20
hiro/core/attribute.hpp
Normal 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
|
@ -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"
|
||||
|
@ -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; \
|
||||
|
@ -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;
|
||||
|
@ -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
|
@ -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
|
@ -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; }
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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&;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)));
|
||||
|
@ -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
|
||||
|
||||
//
|
||||
|
||||
|
@ -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
278
ruby/input/joypad/iokit.cpp
Normal 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);
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user