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

View File

@ -29,7 +29,7 @@ using namespace nall;
namespace Emulator { 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";

View File

@ -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)"});
} }
} }
} }

View File

@ -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);

View File

@ -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([&] {

View File

@ -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++;
} }

View File

@ -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);

View File

@ -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;

View File

@ -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();
} }

View File

@ -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

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

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

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

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

View File

@ -48,7 +48,7 @@ namespace hiro {
#include "browser-window.cpp" #include "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"

View File

@ -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; \

View File

@ -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;

View File

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

View File

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

View File

@ -28,6 +28,7 @@
} \ } \
return T(); \ 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; }

View File

@ -11,6 +11,10 @@ auto mTreeView::destruct() -> void {
// //
auto mTreeView::activation() const -> Mouse::Click {
return state.activation;
}
auto mTreeView::append(sTreeViewItem item) -> type& { 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);

View File

@ -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;

View File

@ -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);

View File

@ -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&;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -12,6 +12,7 @@ static auto TreeView_buttonEvent(GtkTreeView*, GdkEventButton* gdkEvent, pTreeVi
static auto TreeView_change(GtkTreeSelection*, pTreeView* p) -> void { p->_updateSelected(); } static auto TreeView_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

View File

@ -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;
}; };

View File

@ -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 {

View File

@ -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)));

View File

@ -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
// //

View File

@ -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

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

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

View File

@ -1,8 +1,9 @@
#include "keyboard/quartz.cpp" #include "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;
}; };