mirror of https://github.com/bsnes-emu/bsnes.git
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 {
|
namespace Emulator {
|
||||||
static const string Name = "bsnes";
|
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 Author = "byuu";
|
||||||
static const string License = "GPLv3";
|
static const string License = "GPLv3";
|
||||||
static const string Website = "https://byuu.org";
|
static const string Website = "https://byuu.org";
|
||||||
|
|
|
@ -115,29 +115,29 @@ auto Presentation::create() -> void {
|
||||||
saveState.setIcon(Icon::Media::Record).setText("Save State");
|
saveState.setIcon(Icon::Media::Record).setText("Save State");
|
||||||
for(uint index : range(QuickStates)) {
|
for(uint index : range(QuickStates)) {
|
||||||
MenuItem item{&saveState};
|
MenuItem item{&saveState};
|
||||||
item.setProperty("name", {"Quick/Slot ", 1 + index});
|
item.setAttribute("name", {"Quick/Slot ", 1 + index});
|
||||||
item.setProperty("title", {"Slot ", 1 + index});
|
item.setAttribute("title", {"Slot ", 1 + index});
|
||||||
item.setText({"Slot ", 1 + index});
|
item.setText({"Slot ", 1 + index});
|
||||||
item.onActivate([=] { program.saveState({"Quick/Slot ", 1 + index}); });
|
item.onActivate([=] { program.saveState({"Quick/Slot ", 1 + index}); });
|
||||||
}
|
}
|
||||||
loadState.setIcon(Icon::Media::Rewind).setText("Load State");
|
loadState.setIcon(Icon::Media::Rewind).setText("Load State");
|
||||||
for(uint index : range(QuickStates)) {
|
for(uint index : range(QuickStates)) {
|
||||||
MenuItem item{&loadState};
|
MenuItem item{&loadState};
|
||||||
item.setProperty("name", {"Quick/Slot ", 1 + index});
|
item.setAttribute("name", {"Quick/Slot ", 1 + index});
|
||||||
item.setProperty("title", {"Slot ", 1 + index});
|
item.setAttribute("title", {"Slot ", 1 + index});
|
||||||
item.setText({"Slot ", 1 + index});
|
item.setText({"Slot ", 1 + index});
|
||||||
item.onActivate([=] { program.loadState({"Quick/Slot ", 1 + index}); });
|
item.onActivate([=] { program.loadState({"Quick/Slot ", 1 + index}); });
|
||||||
}
|
}
|
||||||
loadState.append(MenuSeparator());
|
loadState.append(MenuSeparator());
|
||||||
loadState.append(MenuItem()
|
loadState.append(MenuItem()
|
||||||
.setProperty("name", "Quick/Undo")
|
.setAttribute("name", "Quick/Undo")
|
||||||
.setProperty("title", "Undo Last Save")
|
.setAttribute("title", "Undo Last Save")
|
||||||
.setIcon(Icon::Edit::Undo).setText("Undo Last Save").onActivate([&] {
|
.setIcon(Icon::Edit::Undo).setText("Undo Last Save").onActivate([&] {
|
||||||
program.loadState("Quick/Undo");
|
program.loadState("Quick/Undo");
|
||||||
}));
|
}));
|
||||||
loadState.append(MenuItem()
|
loadState.append(MenuItem()
|
||||||
.setProperty("name", "Quick/Redo")
|
.setAttribute("name", "Quick/Redo")
|
||||||
.setProperty("title", "Redo Last Undo")
|
.setAttribute("title", "Redo Last Undo")
|
||||||
.setIcon(Icon::Edit::Redo).setText("Redo Last Undo").onActivate([&] {
|
.setIcon(Icon::Edit::Redo).setText("Redo Last Undo").onActivate([&] {
|
||||||
program.loadState("Quick/Redo");
|
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);
|
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(); });
|
speedSlowest.setText("50% (Slowest)").setAttribute("multiplier", "2.0").onActivate([&] { program.updateAudioFrequency(); });
|
||||||
speedSlow.setText("75% (Slow)").setProperty("multiplier", "1.333").onActivate([&] { program.updateAudioFrequency(); });
|
speedSlow.setText("75% (Slow)").setAttribute("multiplier", "1.333").onActivate([&] { program.updateAudioFrequency(); });
|
||||||
speedNormal.setText("100% (Normal)").setProperty("multiplier", "1.0").onActivate([&] { program.updateAudioFrequency(); });
|
speedNormal.setText("100% (Normal)").setAttribute("multiplier", "1.0").onActivate([&] { program.updateAudioFrequency(); });
|
||||||
speedFast.setText("150% (Fast)").setProperty("multiplier", "0.667").onActivate([&] { program.updateAudioFrequency(); });
|
speedFast.setText("150% (Fast)").setAttribute("multiplier", "0.667").onActivate([&] { program.updateAudioFrequency(); });
|
||||||
speedFastest.setText("200% (Fastest)").setProperty("multiplier", "0.5").onActivate([&] { program.updateAudioFrequency(); });
|
speedFastest.setText("200% (Fastest)").setAttribute("multiplier", "0.5").onActivate([&] { program.updateAudioFrequency(); });
|
||||||
runMenu.setIcon(Icon::Media::Play).setText("Run Mode");
|
runMenu.setIcon(Icon::Media::Play).setText("Run Mode");
|
||||||
runEmulation.setText("Normal").onActivate([&] {
|
runEmulation.setText("Normal").onActivate([&] {
|
||||||
});
|
});
|
||||||
|
@ -335,7 +335,7 @@ auto Presentation::updateDeviceMenu() -> void {
|
||||||
if(port.name == "Expansion Port" && device.name == "21fx") continue;
|
if(port.name == "Expansion Port" && device.name == "21fx") continue;
|
||||||
|
|
||||||
MenuRadioItem item{menu};
|
MenuRadioItem item{menu};
|
||||||
item.setProperty("deviceID", device.id);
|
item.setAttribute("deviceID", device.id);
|
||||||
item.setText(tr(device.name));
|
item.setText(tr(device.name));
|
||||||
item.onActivate([=] {
|
item.onActivate([=] {
|
||||||
settings(path).setValue(device.name);
|
settings(path).setValue(device.name);
|
||||||
|
@ -363,7 +363,7 @@ auto Presentation::updateDeviceSelections() -> void {
|
||||||
auto deviceID = emulator->connected(port.id);
|
auto deviceID = emulator->connected(port.id);
|
||||||
for(auto& action : menu->actions()) {
|
for(auto& action : menu->actions()) {
|
||||||
if(auto item = action.cast<MenuRadioItem>()) {
|
if(auto item = action.cast<MenuRadioItem>()) {
|
||||||
if(item.property("deviceID").natural() == deviceID) {
|
if(item.attribute("deviceID").natural() == deviceID) {
|
||||||
item.setChecked();
|
item.setChecked();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -385,7 +385,7 @@ auto Presentation::updateSizeMenu() -> void {
|
||||||
uint multipliers = max(1, height / 240);
|
uint multipliers = max(1, height / 240);
|
||||||
for(uint multiplier : range(1, multipliers + 1)) {
|
for(uint multiplier : range(1, multipliers + 1)) {
|
||||||
MenuRadioItem item{&sizeMenu};
|
MenuRadioItem item{&sizeMenu};
|
||||||
item.setProperty("multiplier", multiplier);
|
item.setAttribute("multiplier", multiplier);
|
||||||
item.setText({multiplier, "x (", 240 * multiplier, "p)"});
|
item.setText({multiplier, "x (", 240 * multiplier, "p)"});
|
||||||
item.onActivate([=] {
|
item.onActivate([=] {
|
||||||
settings.video.multiplier = multiplier;
|
settings.video.multiplier = multiplier;
|
||||||
|
@ -394,7 +394,7 @@ auto Presentation::updateSizeMenu() -> void {
|
||||||
sizeGroup.append(item);
|
sizeGroup.append(item);
|
||||||
}
|
}
|
||||||
for(auto item : sizeGroup.objects<MenuRadioItem>()) {
|
for(auto item : sizeGroup.objects<MenuRadioItem>()) {
|
||||||
if(settings.video.multiplier == item.property("multiplier").natural()) {
|
if(settings.video.multiplier == item.attribute("multiplier").natural()) {
|
||||||
item.setChecked();
|
item.setChecked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,11 +413,11 @@ auto Presentation::updateStateMenus() -> void {
|
||||||
|
|
||||||
for(auto& action : saveState.actions()) {
|
for(auto& action : saveState.actions()) {
|
||||||
if(auto item = action.cast<MenuItem>()) {
|
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; })) {
|
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 {
|
} 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()) {
|
for(auto& action : loadState.actions()) {
|
||||||
if(auto item = action.cast<MenuItem>()) {
|
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; })) {
|
if(auto offset = states.find([&](auto& state) { return state.name == name; })) {
|
||||||
item.setEnabled(true);
|
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 {
|
} else {
|
||||||
item.setEnabled(false);
|
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;
|
double frequency = settings.audio.frequency + settings.audio.skew;
|
||||||
if(!settings.video.blocking && settings.audio.blocking) {
|
if(!settings.video.blocking && settings.audio.blocking) {
|
||||||
for(auto item : presentation.speedGroup.objects<MenuRadioItem>()) {
|
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);
|
Emulator::audio.setFrequency(frequency);
|
||||||
|
|
|
@ -54,19 +54,19 @@ auto EnhancementSettings::create() -> void {
|
||||||
|
|
||||||
mode7Label.setText("HD Mode 7 (fast PPU only)").setFont(Font().setBold());
|
mode7Label.setText("HD Mode 7 (fast PPU only)").setFont(Font().setBold());
|
||||||
mode7ScaleLabel.setText("Scale:");
|
mode7ScaleLabel.setText("Scale:");
|
||||||
mode7Scale.append(ComboButtonItem().setText( "240p").setProperty("multiplier", 1));
|
mode7Scale.append(ComboButtonItem().setText( "240p").setAttribute("multiplier", 1));
|
||||||
mode7Scale.append(ComboButtonItem().setText( "480p").setProperty("multiplier", 2));
|
mode7Scale.append(ComboButtonItem().setText( "480p").setAttribute("multiplier", 2));
|
||||||
mode7Scale.append(ComboButtonItem().setText( "720p").setProperty("multiplier", 3));
|
mode7Scale.append(ComboButtonItem().setText( "720p").setAttribute("multiplier", 3));
|
||||||
mode7Scale.append(ComboButtonItem().setText( "960p").setProperty("multiplier", 4));
|
mode7Scale.append(ComboButtonItem().setText( "960p").setAttribute("multiplier", 4));
|
||||||
mode7Scale.append(ComboButtonItem().setText("1200p").setProperty("multiplier", 5));
|
mode7Scale.append(ComboButtonItem().setText("1200p").setAttribute("multiplier", 5));
|
||||||
mode7Scale.append(ComboButtonItem().setText("1440p").setProperty("multiplier", 6));
|
mode7Scale.append(ComboButtonItem().setText("1440p").setAttribute("multiplier", 6));
|
||||||
mode7Scale.append(ComboButtonItem().setText("1680p").setProperty("multiplier", 7));
|
mode7Scale.append(ComboButtonItem().setText("1680p").setAttribute("multiplier", 7));
|
||||||
mode7Scale.append(ComboButtonItem().setText("1920p").setProperty("multiplier", 8));
|
mode7Scale.append(ComboButtonItem().setText("1920p").setAttribute("multiplier", 8));
|
||||||
for(uint n = 1; n <= 8; n++) {
|
for(uint n = 1; n <= 8; n++) {
|
||||||
if(settings.emulator.hack.ppu.mode7.scale == n) mode7Scale.item(n - 1).setSelected();
|
if(settings.emulator.hack.ppu.mode7.scale == n) mode7Scale.item(n - 1).setSelected();
|
||||||
}
|
}
|
||||||
mode7Scale.onChange([&] {
|
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);
|
emulator->configure("Hacks/PPU/Mode7/Scale", settings.emulator.hack.ppu.mode7.scale);
|
||||||
});
|
});
|
||||||
mode7Perspective.setText("Perspective correction").setChecked(settings.emulator.hack.ppu.mode7.perspective).onToggle([&] {
|
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 InputSettings::activeDevice() -> InputDevice& {
|
||||||
auto index = deviceList.selected().property("index").natural();
|
auto index = deviceList.selected().attribute("index").natural();
|
||||||
return activePort().devices[index];
|
return activePort().devices[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ auto InputSettings::reloadDevices() -> void {
|
||||||
uint index = 0;
|
uint index = 0;
|
||||||
for(auto& device : activePort().devices) {
|
for(auto& device : activePort().devices) {
|
||||||
if(device.mappings) { //only display devices that have configurable inputs
|
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++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ auto CheatDatabase::findCheats() -> void {
|
||||||
cheatList.append(ListViewItem()
|
cheatList.append(ListViewItem()
|
||||||
.setCheckable()
|
.setCheckable()
|
||||||
.setText(cheat["description"].text())
|
.setText(cheat["description"].text())
|
||||||
.setProperty("code", code)
|
.setAttribute("code", code)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ auto CheatDatabase::findCheats() -> void {
|
||||||
auto CheatDatabase::addCheats() -> void {
|
auto CheatDatabase::addCheats() -> void {
|
||||||
for(auto item : cheatList.items()) {
|
for(auto item : cheatList.items()) {
|
||||||
if(item.checked()) {
|
if(item.checked()) {
|
||||||
cheatEditor.addCheat({item.text(), item.property("code"), false});
|
cheatEditor.addCheat({item.text(), item.attribute("code"), false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
|
|
@ -25,7 +25,7 @@ auto ManifestViewer::loadManifest() -> void {
|
||||||
auto titles = emulator->titles();
|
auto titles = emulator->titles();
|
||||||
for(uint offset : range(manifests.size())) {
|
for(uint offset : range(manifests.size())) {
|
||||||
ComboButtonItem item{&manifestOption};
|
ComboButtonItem item{&manifestOption};
|
||||||
item.setProperty("manifest", manifests[offset]);
|
item.setAttribute("manifest", manifests[offset]);
|
||||||
item.setText(titles[offset]);
|
item.setText(titles[offset]);
|
||||||
bool verified = false;
|
bool verified = false;
|
||||||
if(offset == 0) verified = program.superFamicom.verified;
|
if(offset == 0) verified = program.superFamicom.verified;
|
||||||
|
@ -41,7 +41,7 @@ auto ManifestViewer::loadManifest() -> void {
|
||||||
auto ManifestViewer::selectManifest() -> void {
|
auto ManifestViewer::selectManifest() -> void {
|
||||||
auto selected = manifestOption.selected();
|
auto selected = manifestOption.selected();
|
||||||
uint offset = selected->offset();
|
uint offset = selected->offset();
|
||||||
manifestView.setText(selected.property("manifest"));
|
manifestView.setText(selected.attribute("manifest"));
|
||||||
string location;
|
string location;
|
||||||
if(offset == 0) location = program.superFamicom.location;
|
if(offset == 0) location = program.superFamicom.location;
|
||||||
if(offset == 1 && program.gameBoy) location = program.gameBoy.location;
|
if(offset == 1 && program.gameBoy) location = program.gameBoy.location;
|
||||||
|
|
|
@ -13,31 +13,31 @@ auto StateWindow::create() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateWindow::show(string name) -> void {
|
auto StateWindow::show(string name) -> void {
|
||||||
setProperty("type", {name.split("/").first(), "/"});
|
setAttribute("type", {name.split("/").first(), "/"});
|
||||||
setProperty("name", {name.split("/").last()});
|
setAttribute("name", {name.split("/").last()});
|
||||||
nameValue.setText(property("name"));
|
nameValue.setText(attribute("name"));
|
||||||
doChange();
|
doChange();
|
||||||
setTitle(!property("name") ? "Add State" : "Rename State");
|
setTitle(!attribute("name") ? "Add State" : "Rename State");
|
||||||
setAlignment(*toolsWindow);
|
setAlignment(*toolsWindow);
|
||||||
setVisible();
|
setVisible();
|
||||||
setFocused();
|
setFocused();
|
||||||
nameValue.setFocused();
|
nameValue.setFocused();
|
||||||
acceptButton.setText(!property("name") ? "Add" : "Rename");
|
acceptButton.setText(!attribute("name") ? "Add" : "Rename");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateWindow::doChange() -> void {
|
auto StateWindow::doChange() -> void {
|
||||||
auto name = nameValue.text().strip();
|
auto name = nameValue.text().strip();
|
||||||
bool valid = name && !name.contains("\\\"\t/:*?<>|");
|
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)
|
//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});
|
nameValue.setBackgroundColor(valid ? Color{} : Color{255, 224, 224});
|
||||||
acceptButton.setEnabled(valid);
|
acceptButton.setEnabled(valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateWindow::doAccept() -> void {
|
auto StateWindow::doAccept() -> void {
|
||||||
string name = {property("type"), nameValue.text().strip()};
|
string name = {attribute("type"), nameValue.text().strip()};
|
||||||
if(acceptButton.text() == "Add") {
|
if(acceptButton.text() == "Add") {
|
||||||
stateManager.createState(name);
|
stateManager.createState(name);
|
||||||
} else {
|
} else {
|
||||||
|
@ -64,23 +64,23 @@ auto StateManager::create() -> void {
|
||||||
stateList.resizeColumns();
|
stateList.resizeColumns();
|
||||||
});
|
});
|
||||||
categoryLabel.setText("Category:");
|
categoryLabel.setText("Category:");
|
||||||
categoryOption.append(ComboButtonItem().setText("Managed States").setProperty("type", "Managed/"));
|
categoryOption.append(ComboButtonItem().setText("Managed States").setAttribute("type", "Managed/"));
|
||||||
categoryOption.append(ComboButtonItem().setText("Quick States").setProperty("type", "Quick/"));
|
categoryOption.append(ComboButtonItem().setText("Quick States").setAttribute("type", "Quick/"));
|
||||||
categoryOption.onChange([&] { loadStates(); });
|
categoryOption.onChange([&] { loadStates(); });
|
||||||
statePreviewSeparator1.setColor({192, 192, 192});
|
statePreviewSeparator1.setColor({192, 192, 192});
|
||||||
statePreviewLabel.setFont(Font().setBold()).setText("Preview");
|
statePreviewLabel.setFont(Font().setBold()).setText("Preview");
|
||||||
statePreviewSeparator2.setColor({192, 192, 192});
|
statePreviewSeparator2.setColor({192, 192, 192});
|
||||||
loadButton.setText("Load").onActivate([&] {
|
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([&] {
|
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([&] {
|
addButton.setText("Add").onActivate([&] {
|
||||||
stateWindow.show(type());
|
stateWindow.show(type());
|
||||||
});
|
});
|
||||||
editButton.setText("Rename").onActivate([&] {
|
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([&] {
|
removeButton.setText("Remove").onActivate([&] {
|
||||||
removeStates();
|
removeStates();
|
||||||
|
@ -88,7 +88,7 @@ auto StateManager::create() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::type() const -> string {
|
auto StateManager::type() const -> string {
|
||||||
return categoryOption.selected().property("type");
|
return categoryOption.selected().attribute("type");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::loadStates() -> void {
|
auto StateManager::loadStates() -> void {
|
||||||
|
@ -98,7 +98,7 @@ auto StateManager::loadStates() -> void {
|
||||||
auto type = this->type();
|
auto type = this->type();
|
||||||
for(auto& state : program.availableStates(type)) {
|
for(auto& state : program.availableStates(type)) {
|
||||||
TableViewItem item{&stateList};
|
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(state.name.trimLeft(type, 1L)));
|
||||||
item.append(TableViewCell().setText(chrono::local::datetime(state.date)));
|
item.append(TableViewCell().setText(chrono::local::datetime(state.date)));
|
||||||
}
|
}
|
||||||
|
@ -111,19 +111,19 @@ auto StateManager::createState(string name) -> void {
|
||||||
}
|
}
|
||||||
program.saveState(name);
|
program.saveState(name);
|
||||||
for(auto& item : stateList.items()) {
|
for(auto& item : stateList.items()) {
|
||||||
item.setSelected(item.property("name") == name);
|
item.setSelected(item.attribute("name") == name);
|
||||||
}
|
}
|
||||||
stateList.doChange();
|
stateList.doChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::modifyState(string name) -> void {
|
auto StateManager::modifyState(string name) -> void {
|
||||||
if(auto item = stateList.selected()) {
|
if(auto item = stateList.selected()) {
|
||||||
string from = item.property("name");
|
string from = item.attribute("name");
|
||||||
string to = name;
|
string to = name;
|
||||||
if(from != to) {
|
if(from != to) {
|
||||||
program.renameState(from, to);
|
program.renameState(from, to);
|
||||||
for(auto& item : stateList.items()) {
|
for(auto& item : stateList.items()) {
|
||||||
item.setSelected(item.property("name") == to);
|
item.setSelected(item.attribute("name") == to);
|
||||||
}
|
}
|
||||||
stateList.doChange();
|
stateList.doChange();
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ auto StateManager::removeStates() -> void {
|
||||||
if(MessageDialog("Are you sure you want to permanently remove the selected state(s)?")
|
if(MessageDialog("Are you sure you want to permanently remove the selected state(s)?")
|
||||||
.setAlignment(*toolsWindow).question() == "Yes") {
|
.setAlignment(*toolsWindow).question() == "Yes") {
|
||||||
auto lock = acquire();
|
auto lock = acquire();
|
||||||
for(auto& item : batched) program.removeState(item.property("name"));
|
for(auto& item : batched) program.removeState(item.attribute("name"));
|
||||||
loadStates();
|
loadStates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ auto StateManager::updateSelection() -> void {
|
||||||
|
|
||||||
statePreview.setColor({0, 0, 0});
|
statePreview.setColor({0, 0, 0});
|
||||||
if(batched.size() == 1) {
|
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)) {
|
if(saveState.size() >= 3 * sizeof(uint)) {
|
||||||
uint signature = memory::readl<sizeof(uint)>(saveState.data() + 0 * sizeof(uint));
|
uint signature = memory::readl<sizeof(uint)>(saveState.data() + 0 * sizeof(uint));
|
||||||
uint serializer = memory::readl<sizeof(uint)>(saveState.data() + 1 * 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 {
|
auto StateManager::stateEvent(string name) -> void {
|
||||||
if(locked() || !name.beginsWith(type())) return;
|
if(locked() || !name.beginsWith(type())) return;
|
||||||
auto selected = stateList.selected().property("name");
|
auto selected = stateList.selected().attribute("name");
|
||||||
loadStates();
|
loadStates();
|
||||||
for(auto& item : stateList.items()) {
|
for(auto& item : stateList.items()) {
|
||||||
item.setSelected(item.property("name") == selected);
|
item.setSelected(item.attribute("name") == selected);
|
||||||
}
|
}
|
||||||
stateList.doChange();
|
stateList.doChange();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
#define Hiro_BrowserWindow
|
#define Hiro_BrowserWindow
|
||||||
#define Hiro_MessageWindow
|
#define Hiro_MessageWindow
|
||||||
|
|
||||||
#define Hiro_Property
|
#define Hiro_Attribute
|
||||||
|
|
||||||
#define Hiro_Object
|
#define Hiro_Object
|
||||||
#define Hiro_Group
|
#define Hiro_Group
|
||||||
|
|
|
@ -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
|
|
@ -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 "browser-window.cpp"
|
||||||
#include "message-window.cpp"
|
#include "message-window.cpp"
|
||||||
|
|
||||||
#include "property.cpp"
|
#include "attribute.cpp"
|
||||||
|
|
||||||
#include "object.cpp"
|
#include "object.cpp"
|
||||||
#include "group.cpp"
|
#include "group.cpp"
|
||||||
|
|
|
@ -352,6 +352,7 @@ struct Keyboard {
|
||||||
#if defined(Hiro_Mouse)
|
#if defined(Hiro_Mouse)
|
||||||
struct Mouse {
|
struct Mouse {
|
||||||
enum class Button : uint { Left, Middle, Right };
|
enum class Button : uint { Left, Middle, Right };
|
||||||
|
enum class Click : uint { Single, Double };
|
||||||
|
|
||||||
Mouse() = delete;
|
Mouse() = delete;
|
||||||
|
|
||||||
|
@ -410,7 +411,7 @@ struct MessageWindow {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "property.hpp"
|
#include "attribute.hpp"
|
||||||
|
|
||||||
#define Declare(Name) \
|
#define Declare(Name) \
|
||||||
using type = m##Name; \
|
using type = m##Name; \
|
||||||
|
|
|
@ -43,45 +43,43 @@ struct mObject {
|
||||||
virtual auto setVisible(bool visible = true) -> type&;
|
virtual auto setVisible(bool visible = true) -> type&;
|
||||||
auto visible(bool recursive = false) const -> bool;
|
auto visible(bool recursive = false) const -> bool;
|
||||||
|
|
||||||
template<typename T = string> auto property(const string& name) const -> T {
|
template<typename T = string> auto attribute(const string& name) const -> T {
|
||||||
if(auto property = state.properties.find(name)) {
|
if(auto attribute = state.attributes.find(name)) {
|
||||||
if(property->value().is<T>()) return property->value().get<T>();
|
if(attribute->value().is<T>()) return attribute->value().get<T>();
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
//this template basically disables implicit template type deduction:
|
//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 setAttribute(name, value) is called without a type, the type will be a string, so attribute(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<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.
|
//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.
|
//U defaults to a string, so that setAttribute(name, {values, ...}) will deduce U as a string.
|
||||||
template<typename T = string, typename U = string> auto setProperty(const string& name, const U& value) -> type& {
|
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>) {
|
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(auto attribute = state.attributes.find(name)) {
|
||||||
if((const T&)value) property->setValue((const T&)value);
|
if((const T&)value) attribute->setValue((const T&)value);
|
||||||
else state.properties.remove(*property);
|
else state.attributes.remove(*attribute);
|
||||||
} else {
|
} 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;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
//private:
|
//private:
|
||||||
//sizeof(mObject) == 88
|
|
||||||
struct State {
|
struct State {
|
||||||
Font font; //32
|
set<Attribute> attributes;
|
||||||
set<Property> properties; //16
|
Font font;
|
||||||
mObject* parent = nullptr; // 8
|
mObject* parent = nullptr;
|
||||||
int offset = -1; // 4
|
int offset = -1;
|
||||||
char enabled = true; // 1+
|
char enabled = true;
|
||||||
char visible = true; // 1=4
|
char visible = true;
|
||||||
} state;
|
} state;
|
||||||
|
|
||||||
wObject instance; // 8
|
wObject instance;
|
||||||
pObject* delegate = nullptr; // 8
|
pObject* delegate = nullptr;
|
||||||
//vtable // 8
|
|
||||||
|
|
||||||
virtual auto construct() -> void;
|
virtual auto construct() -> void;
|
||||||
virtual auto destruct() -> 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(); \
|
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 enabled(bool recursive = false) const { return self().enabled(recursive); } \
|
||||||
auto focused() const { return self().focused(); } \
|
auto focused() const { return self().focused(); } \
|
||||||
auto font(bool recursive = false) const { return self().font(recursive); } \
|
auto font(bool recursive = false) const { return self().font(recursive); } \
|
||||||
|
@ -38,12 +39,11 @@
|
||||||
} \
|
} \
|
||||||
return Object(); \
|
return Object(); \
|
||||||
} \
|
} \
|
||||||
template<typename T = string> auto property(const string& name) const { return self().property<T>(name); } \
|
|
||||||
auto remove() { return self().remove(), *this; } \
|
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 setEnabled(bool enabled = true) { return self().setEnabled(enabled), *this; } \
|
||||||
auto setFocused() { return self().setFocused(), *this; } \
|
auto setFocused() { return self().setFocused(), *this; } \
|
||||||
auto setFont(const Font& font = {}) { return self().setFont(font), *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 setVisible(bool visible = true) { return self().setVisible(visible), *this; } \
|
||||||
auto visible(bool recursive = false) const { return self().visible(recursive); } \
|
auto visible(bool recursive = false) const { return self().visible(recursive); } \
|
||||||
|
|
||||||
|
@ -794,6 +794,7 @@ struct TreeViewItem : sTreeViewItem {
|
||||||
struct TreeView : sTreeView {
|
struct TreeView : sTreeView {
|
||||||
DeclareSharedWidget(TreeView)
|
DeclareSharedWidget(TreeView)
|
||||||
|
|
||||||
|
auto activation() const { return self().activation(); }
|
||||||
auto append(sTreeViewItem item) { return self().append(item), *this; }
|
auto append(sTreeViewItem item) { return self().append(item), *this; }
|
||||||
auto backgroundColor() const { return self().backgroundColor(); }
|
auto backgroundColor() const { return self().backgroundColor(); }
|
||||||
auto collapse(bool recursive = true) { return self().collapse(recursive), *this; }
|
auto collapse(bool recursive = true) { return self().collapse(recursive), *this; }
|
||||||
|
@ -814,6 +815,7 @@ struct TreeView : sTreeView {
|
||||||
auto reset() { return self().reset(), *this; }
|
auto reset() { return self().reset(), *this; }
|
||||||
auto selectNone() { return self().selectNone(), *this; }
|
auto selectNone() { return self().selectNone(), *this; }
|
||||||
auto selected() const { return self().selected(); }
|
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 setBackgroundColor(Color color = {}) { return self().setBackgroundColor(color), *this; }
|
||||||
auto setForegroundColor(Color color = {}) { return self().setForegroundColor(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 setFrameSize(Size size) { return self().setFrameSize(size), *this; }
|
||||||
auto setFullScreen(bool fullScreen = true) { return self().setFullScreen(fullScreen), *this; }
|
auto setFullScreen(bool fullScreen = true) { return self().setFullScreen(fullScreen), *this; }
|
||||||
auto setGeometry(Geometry geometry) { return self().setGeometry(geometry), *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 setMaximized(bool maximized) { return self().setMaximized(maximized), *this; }
|
||||||
auto setMaximumSize(Size size = {}) { return self().setMaximumSize(size), *this; }
|
auto setMaximumSize(Size size = {}) { return self().setMaximumSize(size), *this; }
|
||||||
auto setMinimized(bool minimized) { return self().setMinimized(minimized), *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& {
|
auto mTreeView::append(sTreeViewItem item) -> type& {
|
||||||
state.items.append(item);
|
state.items.append(item);
|
||||||
item->setParent(this, itemCount() - 1);
|
item->setParent(this, itemCount() - 1);
|
||||||
|
@ -119,6 +123,12 @@ auto mTreeView::selected() const -> TreeViewItem {
|
||||||
return item(state.selectedPath);
|
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& {
|
auto mTreeView::setBackgroundColor(Color color) -> type& {
|
||||||
state.backgroundColor = color;
|
state.backgroundColor = color;
|
||||||
signal(setBackgroundColor, color);
|
signal(setBackgroundColor, color);
|
||||||
|
|
|
@ -3,6 +3,7 @@ struct mTreeView : mWidget {
|
||||||
Declare(TreeView)
|
Declare(TreeView)
|
||||||
using mObject::remove;
|
using mObject::remove;
|
||||||
|
|
||||||
|
auto activation() const -> Mouse::Click;
|
||||||
auto append(sTreeViewItem item) -> type&;
|
auto append(sTreeViewItem item) -> type&;
|
||||||
auto backgroundColor() const -> Color;
|
auto backgroundColor() const -> Color;
|
||||||
auto collapse(bool recursive = true) -> type&;
|
auto collapse(bool recursive = true) -> type&;
|
||||||
|
@ -23,12 +24,14 @@ struct mTreeView : mWidget {
|
||||||
auto reset() -> type&;
|
auto reset() -> type&;
|
||||||
auto selectNone() -> type&;
|
auto selectNone() -> type&;
|
||||||
auto selected() const -> TreeViewItem;
|
auto selected() const -> TreeViewItem;
|
||||||
|
auto setActivation(Mouse::Click activation = Mouse::Click::Double) -> type&;
|
||||||
auto setBackgroundColor(Color color = {}) -> type&;
|
auto setBackgroundColor(Color color = {}) -> type&;
|
||||||
auto setForegroundColor(Color color = {}) -> type&;
|
auto setForegroundColor(Color color = {}) -> type&;
|
||||||
auto setParent(mObject* parent = nullptr, int offset = -1) -> type&;
|
auto setParent(mObject* parent = nullptr, int offset = -1) -> type&;
|
||||||
|
|
||||||
//private:
|
//private:
|
||||||
struct State {
|
struct State {
|
||||||
|
Mouse::Click activation = Mouse::Click::Double;
|
||||||
Color backgroundColor;
|
Color backgroundColor;
|
||||||
Color foregroundColor;
|
Color foregroundColor;
|
||||||
vector<sTreeViewItem> items;
|
vector<sTreeViewItem> items;
|
||||||
|
|
|
@ -279,6 +279,17 @@ auto mWindow::setGeometry(Geometry geometry) -> type& {
|
||||||
return *this;
|
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& {
|
auto mWindow::setMaximized(bool maximized) -> type& {
|
||||||
state.maximized = maximized;
|
state.maximized = maximized;
|
||||||
signal(setMaximized, maximized);
|
signal(setMaximized, maximized);
|
||||||
|
|
|
@ -49,6 +49,7 @@ struct mWindow : mObject {
|
||||||
auto setFrameSize(Size size) -> type&;
|
auto setFrameSize(Size size) -> type&;
|
||||||
auto setFullScreen(bool fullScreen = true) -> type&;
|
auto setFullScreen(bool fullScreen = true) -> type&;
|
||||||
auto setGeometry(Geometry geometry) -> type&;
|
auto setGeometry(Geometry geometry) -> type&;
|
||||||
|
auto setGeometry(Alignment, Size) -> type&;
|
||||||
auto setMaximized(bool maximized = true) -> type&;
|
auto setMaximized(bool maximized = true) -> type&;
|
||||||
auto setMaximumSize(Size size = {}) -> type&;
|
auto setMaximumSize(Size size = {}) -> type&;
|
||||||
auto setMinimized(bool minimized = true) -> type&;
|
auto setMinimized(bool minimized = true) -> type&;
|
||||||
|
|
|
@ -359,7 +359,7 @@ auto BrowserDialogWindow::run() -> BrowserDialog::Response {
|
||||||
window.setAlignment(state.relativeTo, state.alignment);
|
window.setAlignment(state.relativeTo, state.alignment);
|
||||||
window.setDismissable();
|
window.setDismissable();
|
||||||
window.setVisible();
|
window.setVisible();
|
||||||
fileName.setFocused();
|
fileName.setText(state.name).setFocused().doChange();
|
||||||
Application::processEvents();
|
Application::processEvents();
|
||||||
view->resizeColumns();
|
view->resizeColumns();
|
||||||
window.setModal();
|
window.setModal();
|
||||||
|
@ -480,6 +480,11 @@ auto BrowserDialog::setFilters(const vector<string>& filters) -> type& {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto BrowserDialog::setName(const string& name) -> type& {
|
||||||
|
state.name = name;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
auto BrowserDialog::setOptions(const vector<string>& options) -> type& {
|
auto BrowserDialog::setOptions(const vector<string>& options) -> type& {
|
||||||
state.options = options;
|
state.options = options;
|
||||||
return *this;
|
return *this;
|
||||||
|
|
|
@ -17,6 +17,7 @@ struct BrowserDialog {
|
||||||
auto setAlignment(Alignment = Alignment::Center) -> type&;
|
auto setAlignment(Alignment = Alignment::Center) -> type&;
|
||||||
auto setAlignment(sWindow relativeTo, Alignment = Alignment::Center) -> type&;
|
auto setAlignment(sWindow relativeTo, Alignment = Alignment::Center) -> type&;
|
||||||
auto setFilters(const vector<string>& filters = {}) -> type&;
|
auto setFilters(const vector<string>& filters = {}) -> type&;
|
||||||
|
auto setName(const string& name = "") -> type&;
|
||||||
auto setOptions(const vector<string>& options = {}) -> type&;
|
auto setOptions(const vector<string>& options = {}) -> type&;
|
||||||
auto setPath(const string& path = "") -> type&;
|
auto setPath(const string& path = "") -> type&;
|
||||||
auto setTitle(const string& title = "") -> type&;
|
auto setTitle(const string& title = "") -> type&;
|
||||||
|
@ -26,6 +27,7 @@ private:
|
||||||
string action;
|
string action;
|
||||||
Alignment alignment = Alignment::Center;
|
Alignment alignment = Alignment::Center;
|
||||||
vector<string> filters = {"*"};
|
vector<string> filters = {"*"};
|
||||||
|
string name;
|
||||||
vector<string> options;
|
vector<string> options;
|
||||||
string path;
|
string path;
|
||||||
sWindow relativeTo;
|
sWindow relativeTo;
|
||||||
|
|
|
@ -200,13 +200,13 @@ auto pTableView::_cellWidth(unsigned _row, unsigned _column) -> unsigned {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pTableView::_columnWidth(unsigned _column) -> unsigned {
|
auto pTableView::_columnWidth(unsigned _column) -> unsigned {
|
||||||
unsigned width = 8;
|
unsigned width = 6;
|
||||||
if(auto column = self().column(_column)) {
|
if(auto column = self().column(_column)) {
|
||||||
if(auto& icon = column->state.icon) {
|
if(auto& icon = column->state.icon) {
|
||||||
width += icon.width() + 2;
|
width += icon.width() + 2;
|
||||||
}
|
}
|
||||||
if(auto& text = column->state.text) {
|
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) {
|
if(column->state.sorting != Sort::None) {
|
||||||
width += 20;
|
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_change(GtkTreeSelection*, pTreeView* p) -> void { p->_updateSelected(); }
|
||||||
static auto TreeView_context(GtkTreeView*, pTreeView* p) -> void { p->self().doContext(); }
|
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_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); }
|
static auto TreeView_toggle(GtkCellRendererToggle*, char* path, pTreeView* p) -> void { p->_togglePath(path); }
|
||||||
|
|
||||||
auto pTreeView::construct() -> void {
|
auto pTreeView::construct() -> void {
|
||||||
|
@ -52,11 +53,13 @@ auto pTreeView::construct() -> void {
|
||||||
gtk_tree_view_append_column(gtkTreeView, gtkTreeViewColumn);
|
gtk_tree_view_append_column(gtkTreeView, gtkTreeViewColumn);
|
||||||
gtk_tree_view_set_search_column(gtkTreeView, 2);
|
gtk_tree_view_set_search_column(gtkTreeView, 2);
|
||||||
|
|
||||||
|
setActivation(state().activation);
|
||||||
setBackgroundColor(state().backgroundColor);
|
setBackgroundColor(state().backgroundColor);
|
||||||
setForegroundColor(state().foregroundColor);
|
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-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), "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), "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(gtkWidgetChild), "row-activated", G_CALLBACK(TreeView_activate), (gpointer)this);
|
||||||
g_signal_connect(G_OBJECT(gtkTreeSelection), "changed", G_CALLBACK(TreeView_change), (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::remove(sTreeViewItem item) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto pTreeView::setActivation(Mouse::Click activation) -> void {
|
||||||
|
//handled by callbacks
|
||||||
|
}
|
||||||
|
|
||||||
auto pTreeView::setBackgroundColor(Color color) -> void {
|
auto pTreeView::setBackgroundColor(Color color) -> void {
|
||||||
auto gdkColor = CreateColor(color);
|
auto gdkColor = CreateColor(color);
|
||||||
gtk_widget_modify_base(gtkWidgetChild, GTK_STATE_NORMAL, color ? &gdkColor : nullptr);
|
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 {
|
auto pTreeView::_activatePath(GtkTreePath* gtkPath) -> void {
|
||||||
|
if(suppressActivate) {
|
||||||
|
suppressActivate = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
char* path = gtk_tree_path_to_string(gtkPath);
|
char* path = gtk_tree_path_to_string(gtkPath);
|
||||||
if(auto item = self().item(string{path}.transform(":", "/"))) {
|
if(auto item = self().item(string{path}.transform(":", "/"))) {
|
||||||
if(!locked()) self().doActivate();
|
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) {
|
if(gdkEvent->button == 3) {
|
||||||
//multi-selection mode: (not implemented in TreeView yet ... but code is here anyway for future use)
|
//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
|
//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 append(sTreeViewItem item) -> void;
|
||||||
auto remove(sTreeViewItem item) -> void;
|
auto remove(sTreeViewItem item) -> void;
|
||||||
|
auto setActivation(Mouse::Click activation) -> void;
|
||||||
auto setBackgroundColor(Color color) -> void;
|
auto setBackgroundColor(Color color) -> void;
|
||||||
auto setFocused() -> void override;
|
auto setFocused() -> void override;
|
||||||
auto setForegroundColor(Color color) -> void;
|
auto setForegroundColor(Color color) -> void;
|
||||||
|
@ -32,6 +33,7 @@ struct pTreeView : pWidget {
|
||||||
GtkCellRenderer* gtkCellPixbuf = nullptr;
|
GtkCellRenderer* gtkCellPixbuf = nullptr;
|
||||||
GtkCellRenderer* gtkCellText = nullptr;
|
GtkCellRenderer* gtkCellText = nullptr;
|
||||||
GtkEntry* gtkEntry = nullptr;
|
GtkEntry* gtkEntry = nullptr;
|
||||||
|
bool suppressActivate = false;
|
||||||
bool suppressChange = false;
|
bool suppressChange = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -317,13 +317,15 @@ auto pWindow::setFullScreen(bool fullScreen) -> void {
|
||||||
} else {
|
} else {
|
||||||
gtk_window_unfullscreen(GTK_WINDOW(widget));
|
gtk_window_unfullscreen(GTK_WINDOW(widget));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto time = chrono::millisecond();
|
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 pWindow::setGeometry(Geometry geometry) -> void {
|
||||||
auto margin = frameMargin();
|
auto margin = frameMargin();
|
||||||
gtk_window_move(GTK_WINDOW(widget), geometry.x() - margin.x(), geometry.y() - margin.y());
|
|
||||||
|
|
||||||
setMaximumSize(state().maximumSize);
|
setMaximumSize(state().maximumSize);
|
||||||
setMinimumSize(state().minimumSize);
|
setMinimumSize(state().minimumSize);
|
||||||
|
@ -333,10 +335,13 @@ auto pWindow::setGeometry(Geometry geometry) -> void {
|
||||||
Application::processEvents();
|
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());
|
gtk_window_resize(GTK_WINDOW(widget), geometry.width(), geometry.height() + _menuHeight() + _statusHeight());
|
||||||
|
|
||||||
auto time2 = chrono::millisecond();
|
auto time2 = chrono::millisecond();
|
||||||
while(chrono::millisecond() - time2 < 20) Application::processEvents();
|
while(chrono::millisecond() - time2 < 20) {
|
||||||
|
Application::processEvents();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pWindow::setMaximized(bool maximized) -> void {
|
auto pWindow::setMaximized(bool maximized) -> void {
|
||||||
|
|
|
@ -44,7 +44,13 @@ auto pComboButton::reset() -> void {
|
||||||
SendMessage(hwnd, CB_RESETCONTENT, 0, 0);
|
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 {
|
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;
|
RECT rc;
|
||||||
GetWindowRect(hwnd, &rc);
|
GetWindowRect(hwnd, &rc);
|
||||||
geometry.setY(geometry.y() + (geometry.height() - (rc.bottom - rc.top)));
|
geometry.setY(geometry.y() + (geometry.height() - (rc.bottom - rc.top)));
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
#include <nall/range.hpp>
|
#include <nall/range.hpp>
|
||||||
#include <nall/serializer.hpp>
|
#include <nall/serializer.hpp>
|
||||||
#include <nall/stdint.hpp>
|
#include <nall/stdint.hpp>
|
||||||
|
#if !defined(PLATFORM_ANDROID)
|
||||||
#include <nall/cipher/chacha20.hpp>
|
#include <nall/cipher/chacha20.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(PLATFORM_LINUX) && __has_include(<sys/random.h>)
|
#if defined(PLATFORM_LINUX) && __has_include(<sys/random.h>)
|
||||||
#include <sys/random.h>
|
#include <sys/random.h>
|
||||||
|
@ -125,6 +127,7 @@ private:
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(PLATFORM_ANDROID)
|
||||||
namespace CSPRNG {
|
namespace CSPRNG {
|
||||||
|
|
||||||
//XChaCha20 cryptographically secure pseudo-random number generator
|
//XChaCha20 cryptographically secure pseudo-random number generator
|
||||||
|
@ -153,6 +156,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ ifeq ($(ruby),)
|
||||||
else ifeq ($(platform),macos)
|
else ifeq ($(platform),macos)
|
||||||
ruby += video.cgl
|
ruby += video.cgl
|
||||||
ruby += audio.openal
|
ruby += audio.openal
|
||||||
ruby += input.quartz input.carbon
|
ruby += input.quartz #input.carbon
|
||||||
else ifeq ($(platform),linux)
|
else ifeq ($(platform),linux)
|
||||||
ruby += video.glx video.glx2 video.xvideo video.xshm
|
ruby += video.glx video.glx2 video.xvideo video.xshm
|
||||||
ruby += audio.oss audio.alsa audio.openal audio.pulseaudio audio.pulseaudiosimple audio.ao
|
ruby += audio.oss audio.alsa audio.openal audio.pulseaudio audio.pulseaudiosimple audio.ao
|
||||||
|
|
|
@ -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 "keyboard/quartz.cpp"
|
||||||
|
#include "joypad/iokit.cpp"
|
||||||
|
|
||||||
struct InputQuartz : InputDriver {
|
struct InputQuartz : InputDriver {
|
||||||
InputQuartz& self = *this;
|
InputQuartz& self = *this;
|
||||||
InputQuartz(Input& super) : InputDriver(super), keyboard(super) {}
|
InputQuartz(Input& super) : InputDriver(super), keyboard(super), joypad(super) {}
|
||||||
~InputQuartz() { terminate(); }
|
~InputQuartz() { terminate(); }
|
||||||
|
|
||||||
auto create() -> bool override {
|
auto create() -> bool override {
|
||||||
|
@ -19,6 +20,7 @@ struct InputQuartz : InputDriver {
|
||||||
auto poll() -> vector<shared_pointer<HID::Device>> override {
|
auto poll() -> vector<shared_pointer<HID::Device>> override {
|
||||||
vector<shared_pointer<HID::Device>> devices;
|
vector<shared_pointer<HID::Device>> devices;
|
||||||
keyboard.poll(devices);
|
keyboard.poll(devices);
|
||||||
|
joypad.poll(devices);
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,14 +32,17 @@ private:
|
||||||
auto initialize() -> bool {
|
auto initialize() -> bool {
|
||||||
terminate();
|
terminate();
|
||||||
if(!keyboard.initialize()) return false;
|
if(!keyboard.initialize()) return false;
|
||||||
|
if(!joypad.initialize()) return false;
|
||||||
return isReady = true;
|
return isReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto terminate() -> void {
|
auto terminate() -> void {
|
||||||
isReady = false;
|
isReady = false;
|
||||||
keyboard.terminate();
|
keyboard.terminate();
|
||||||
|
joypad.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isReady = false;
|
bool isReady = false;
|
||||||
InputKeyboardQuartz keyboard;
|
InputKeyboardQuartz keyboard;
|
||||||
|
InputJoypadIOKit joypad;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue