diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index cd864acf..4a53bcb7 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -13,7 +13,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "106.53"; + static const string Version = "106.54"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/higan/target-bsnes/bsnes.hpp b/higan/target-bsnes/bsnes.hpp index 7a93071c..862c81ce 100644 --- a/higan/target-bsnes/bsnes.hpp +++ b/higan/target-bsnes/bsnes.hpp @@ -11,6 +11,9 @@ extern Input input; #include extern unique_pointer emulator; +#include +#include + #include "program/program.hpp" #include "input/input.hpp" #include "presentation/presentation.hpp" diff --git a/higan/target-bsnes/presentation/presentation.cpp b/higan/target-bsnes/presentation/presentation.cpp index 40961616..fe11b08e 100644 --- a/higan/target-bsnes/presentation/presentation.cpp +++ b/higan/target-bsnes/presentation/presentation.cpp @@ -448,9 +448,8 @@ auto Presentation::updateStateMenus() -> void { for(auto& action : saveState.actions()) { if(auto item = action.cast()) { if(auto name = item.property("name")) { - if(states.find(name)) { - auto timestamp = program.stateTimestamp(item.property("name")); - item.setText({item.property("title"), " [", chrono::local::datetime(timestamp), "]"}); + if(auto offset = states.find([&](auto& state) { return state.name == name; })) { + item.setText({item.property("title"), " [", chrono::local::datetime(states[*offset].date), "]"}); } else { item.setText({item.property("title"), " [Empty]"}); } @@ -461,10 +460,9 @@ auto Presentation::updateStateMenus() -> void { for(auto& action : loadState.actions()) { if(auto item = action.cast()) { if(auto name = item.property("name")) { - if(states.find(name)) { - auto timestamp = program.stateTimestamp(item.property("name")); + if(auto offset = states.find([&](auto& state) { return state.name == name; })) { item.setEnabled(true); - item.setText({item.property("title"), " [", chrono::local::datetime(timestamp), "]"}); + item.setText({item.property("title"), " [", chrono::local::datetime(states[*offset].date), "]"}); } else { item.setEnabled(false); item.setText({item.property("title"), " [Empty]"}); diff --git a/higan/target-bsnes/program/paths.cpp b/higan/target-bsnes/program/paths.cpp index 3efd2a7e..743d0fd6 100644 --- a/higan/target-bsnes/program/paths.cpp +++ b/higan/target-bsnes/program/paths.cpp @@ -73,14 +73,14 @@ auto Program::screenshotPath() -> string { if(!emulator->loaded()) return ""; auto location = gamePath(); if(location.endsWith("/")) { - directory::create({location, "bsnes/screenshots/"}); - location = {location, "bsnes/screenshots/capture"}; + location = {location, "bsnes/screenshots/"}; + directory::create(location); } else { - location = path("Screenshots", location); + location = {path("Screenshots", location), "-"}; } - for(uint n : range(1, 999)) { - string filename = {location, ".", pad(n, 3, '0'), ".bmp"}; + for(uint n : range(1, 1000)) { + string filename = {location, pad(n, 3, '0'), ".bmp"}; if(!file::exists(filename)) return filename; } - return {location, ".000.bmp"}; + return {location, "000.bmp"}; } diff --git a/higan/target-bsnes/program/platform.cpp b/higan/target-bsnes/program/platform.cpp index 73c276b4..6d0633fe 100644 --- a/higan/target-bsnes/program/platform.cpp +++ b/higan/target-bsnes/program/platform.cpp @@ -196,20 +196,20 @@ auto Program::videoRefresh(uint display, const uint32* data, uint pitch, uint wi uint32_t* output; uint length; + //this relies on the UI only running between Emulator::Scheduler::Event::Frame events + //this will always be the case; so we can avoid an unnecessary copy or one-frame delay here + //if the core were to exit between a frame event, the next frame might've been only partially rendered + screenshot.data = data; + screenshot.pitch = pitch; + screenshot.width = width; + screenshot.height = height; + pitch >>= 2; if(presentation.overscanCropping.checked()) { if(height == 240) data += 8 * pitch, height -= 16; if(height == 480) data += 16 * pitch, height -= 32; } - //this relies on the UI only running between Emulator::Scheduler::Event::Frame events - //this will always be the case; so we can avoid an unnecessary copy or one-frame delay here - //if the core were to exit between a frame event, the next frame might've been only partially rendered - screenshot.data = data; - screenshot.pitch = pitch << 2; - screenshot.width = width; - screenshot.height = height; - if(video.acquire(output, length, width, height)) { length >>= 2; diff --git a/higan/target-bsnes/program/program.hpp b/higan/target-bsnes/program/program.hpp index 660bc44e..f71d63d7 100644 --- a/higan/target-bsnes/program/program.hpp +++ b/higan/target-bsnes/program/program.hpp @@ -49,8 +49,14 @@ struct Program : Emulator::Platform { auto screenshotPath() -> string; //states.cpp - auto availableStates(string type) -> vector; - auto stateTimestamp(string filename) -> uint64_t; + struct State { + string name; + uint64_t date; + static const uint Signature; + }; + auto availableStates(string type) -> vector; + auto hasState(string filename) -> bool; + auto loadStateData(string filename) -> vector; auto loadState(string filename) -> bool; auto saveState(string filename) -> bool; auto saveUndoState() -> bool; diff --git a/higan/target-bsnes/program/states.cpp b/higan/target-bsnes/program/states.cpp index 11a38b2e..83436e3c 100644 --- a/higan/target-bsnes/program/states.cpp +++ b/higan/target-bsnes/program/states.cpp @@ -1,53 +1,42 @@ -auto Program::availableStates(string type) -> vector { - vector result; +const uint Program::State::Signature = 0x5a22'0000; + +auto Program::availableStates(string type) -> vector { + vector result; if(!emulator->loaded()) return result; if(gamePath().endsWith("/")) { for(auto& file : directory::ifiles({statePath(), type}, "*.bst")) { - result.append({type, file.trimRight(".bst", 1L)}); + auto timestamp = file::timestamp({statePath(), type, file}, file::time::modify); + result.append({{type, file.trimRight(".bst", 1L)}, timestamp}); } } else { Decode::ZIP input; if(input.open(statePath())) { - vector filenames; for(auto& file : input.file) { - if(file.name.match({type, "*.bst"})) result.append(file.name.trimRight(".bst", 1L)); + if(!file.name.match({type, "*.bst"})) continue; + result.append({file.name.trimRight(".bst", 1L), (uint64_t)file.timestamp}); } } } - result.isort(); return result; } -auto Program::stateTimestamp(string filename) -> uint64_t { - auto timestamp = chrono::timestamp(); - if(!emulator->loaded()) return timestamp; - - if(gamePath().endsWith("/")) { - string location = {statePath(), filename, ".bst"}; - timestamp = file::timestamp(location, file::time::modify); - } else { - string location = {filename, ".bst"}; - Decode::ZIP input; - if(input.open(statePath())) { - for(auto& file : input.file) { - if(file.name != location) continue; - timestamp = file.timestamp; - break; - } - } - } - - return timestamp; -} - -auto Program::loadState(string filename) -> bool { +auto Program::hasState(string filename) -> bool { if(!emulator->loaded()) return false; - string prefix = Location::file(filename); - vector memory; + if(gamePath().endsWith("/")) { + return file::exists({statePath(), filename, ".bst"}); + } else { + auto type = string{filename.split("/").first(), "/"}; + return (bool)availableStates(type).find([&](auto& state) { return state.name == filename; }); + } +} +auto Program::loadStateData(string filename) -> vector { + if(!emulator->loaded()) return {}; + + vector memory; if(gamePath().endsWith("/")) { string location = {statePath(), filename, ".bst"}; memory = file::read(location); @@ -63,10 +52,18 @@ auto Program::loadState(string filename) -> bool { } } - if(memory) { + if(memory.size() < 3 * sizeof(uint)) return {}; //too small to be a valid state file + if(memory::readl(memory.data()) != State::Signature) return {}; //wrong format or version + return memory; +} + +auto Program::loadState(string filename) -> bool { + string prefix = Location::file(filename); + if(auto memory = loadStateData(filename)) { if(filename != "quick/undo") saveUndoState(); if(filename == "quick/undo") saveRedoState(); - serializer s{memory.data(), memory.size()}; + auto serializerRLE = Decode::RLE(memory.data() + 3 * sizeof(uint)); + serializer s{serializerRLE.data(), serializerRLE.size()}; if(!emulator->unserialize(s)) return showMessage({"[", prefix, "] is in incompatible format"}), false; return showMessage({"Loaded [", prefix, "]"}), true; } else { @@ -76,15 +73,32 @@ auto Program::loadState(string filename) -> bool { auto Program::saveState(string filename) -> bool { if(!emulator->loaded()) return false; - string prefix = Location::file(filename); + serializer s = emulator->serialize(); if(!s.size()) return showMessage({"Failed to save [", prefix, "]"}), false; + auto serializerRLE = Encode::RLE(s.data(), s.size()); + + image preview; + preview.allocate(screenshot.data, screenshot.pitch, screenshot.width, screenshot.height); + if(preview.width() != 256 || preview.height() != 240) preview.scale(256, 240, true); + preview.transform(0, 15, 0x8000, 0x7c00, 0x03e0, 0x001f); + auto previewRLE = Encode::RLE(preview.data(), preview.size()); + + vector saveState; + saveState.resize(3 * sizeof(uint)); + memory::writel(saveState.data() + 0 * sizeof(uint), State::Signature); + memory::writel(saveState.data() + 1 * sizeof(uint), serializerRLE.size()); + memory::writel(saveState.data() + 2 * sizeof(uint), previewRLE.size()); + saveState.append(serializerRLE); + saveState.append(previewRLE); if(gamePath().endsWith("/")) { string location = {statePath(), filename, ".bst"}; directory::create(Location::path(location)); - if(!file::write(location, s.data(), s.size())) return showMessage({"Unable to write [", prefix, "] to disk"}), false; + if(!file::write(location, saveState.data(), saveState.size())) { + return showMessage({"Unable to write [", prefix, "] to disk"}), false; + } } else { string location = {filename, ".bst"}; @@ -105,10 +119,11 @@ auto Program::saveState(string filename) -> bool { for(auto& state : states) { output.append(state.name, state.memory.data(), state.memory.size(), state.timestamp); } - output.append(location, s.data(), s.size()); + output.append(location, saveState.data(), saveState.size()); } if(filename.beginsWith("quick/")) presentation.updateStateMenus(); + stateManager.stateEvent(filename); return showMessage({"Saved [", prefix, "]"}), true; } @@ -132,10 +147,11 @@ auto Program::saveRedoState() -> bool { auto Program::removeState(string filename) -> bool { if(!emulator->loaded()) return false; + bool result = false; if(gamePath().endsWith("/")) { string location = {statePath(), filename, ".bst"}; - return file::remove(location); + result = file::remove(location); } else { bool found = false; string location = {filename, ".bst"}; @@ -162,21 +178,28 @@ auto Program::removeState(string filename) -> bool { file::remove(statePath()); } - return found; + result = found; } + + if(result) { + presentation.updateStateMenus(); + stateManager.stateEvent(filename); + } + return result; } -auto Program::renameState(string from, string to) -> bool { +auto Program::renameState(string from_, string to_) -> bool { if(!emulator->loaded()) return false; + bool result = false; if(gamePath().endsWith("/")) { - from = {statePath(), from, ".bst"}; - to = {statePath(), to, ".bst."}; - return file::rename(from, to); + string from = {statePath(), from_, ".bst"}; + string to = {statePath(), to_, ".bst"}; + result = file::rename(from, to); } else { bool found = false; - from = {from, ".bst"}; - to = {to, ".bst"}; + string from = {from_, ".bst"}; + string to = {to_, ".bst"}; struct State { string name; time_t timestamp; vector memory; }; vector states; @@ -195,6 +218,11 @@ auto Program::renameState(string from, string to) -> bool { output.append(state.name, state.memory.data(), state.memory.size(), state.timestamp); } - return found; + result = found; } + + if(result) { + stateManager.stateEvent(to_); + } + return result; } diff --git a/higan/target-bsnes/program/utility.cpp b/higan/target-bsnes/program/utility.cpp index 98c49a34..eecf4bf1 100644 --- a/higan/target-bsnes/program/utility.cpp +++ b/higan/target-bsnes/program/utility.cpp @@ -34,9 +34,24 @@ auto Program::updateStatus() -> void { auto Program::captureScreenshot() -> bool { if(emulator->loaded() && screenshot.data) { if(auto filename = screenshotPath()) { - if(Encode::BMP::create(filename, - (const uint32_t*)screenshot.data, screenshot.pitch, screenshot.width, screenshot.height, false - )) { + image capture; + capture.allocate(screenshot.data, screenshot.pitch, screenshot.width, screenshot.height); + + //normalize pixel aspect ratio to 1:1 + if(capture.width() == 512 && capture.height() == 240) capture.scale(512, 480, false); //hires + if(capture.width() == 256 && capture.height() == 480) capture.scale(512, 480, false); //interlace + + auto data = capture.data(); + auto pitch = capture.pitch(); + auto width = capture.width(); + auto height = capture.height(); + + if(presentation.overscanCropping.checked()) { + if(height == 240) data += 8 * pitch, height -= 16; + if(height == 480) data += 16 * pitch, height -= 32; + } + + if(Encode::BMP::create(filename, data, width << 2, width, height, /* alpha = */ false)) { showMessage({"Captured screenshot [", Location::file(filename), "]"}); return true; } diff --git a/higan/target-bsnes/settings/drivers.cpp b/higan/target-bsnes/settings/drivers.cpp index fb814e99..60cb8ba5 100644 --- a/higan/target-bsnes/settings/drivers.cpp +++ b/higan/target-bsnes/settings/drivers.cpp @@ -25,6 +25,7 @@ auto DriverSettings::create() -> void { settings["Video/Flush"].setValue(videoFlushToggle.checked()); program.updateVideoFlush(); }); + videoSpacer.setColor({192, 192, 192}); audioLabel.setText("Audio").setFont(Font().setBold()); audioLayout.setSize({2, 2}); @@ -51,6 +52,7 @@ auto DriverSettings::create() -> void { settings["Audio/Dynamic"].setValue(audioDynamicToggle.checked()); program.updateAudioDynamic(); }); + audioSpacer.setColor({192, 192, 192}); inputLabel.setText("Input").setFont(Font().setBold()); inputLayout.setSize({2, 1}); diff --git a/higan/target-bsnes/settings/emulator.cpp b/higan/target-bsnes/settings/emulator.cpp index 862d205a..3d35b1c4 100644 --- a/higan/target-bsnes/settings/emulator.cpp +++ b/higan/target-bsnes/settings/emulator.cpp @@ -39,6 +39,7 @@ auto EmulatorSettings::create() -> void { settings["UserInterface/SuppressScreenSaver"].setValue(suppressScreenSaver.checked()); Application::setScreenSaver(!suppressScreenSaver.checked()); }); + optionsSpacer.setColor({192, 192, 192}); hacksLabel.setText("Hacks").setFont(Font().setBold()); fastPPUOption.setText("Fast PPU").setChecked(settings["Emulator/Hack/FastPPU"].boolean()).onToggle([&] { diff --git a/higan/target-bsnes/settings/hotkeys.cpp b/higan/target-bsnes/settings/hotkeys.cpp index aa46c520..c70ef97f 100644 --- a/higan/target-bsnes/settings/hotkeys.cpp +++ b/higan/target-bsnes/settings/hotkeys.cpp @@ -4,6 +4,7 @@ auto HotkeySettings::create() -> void { layout.setPadding(5); mappingList.setBatchable(); + mappingList.setHeadered(); mappingList.onActivate([&] { if(assignButton.enabled()) assignButton.doActivate(); }); @@ -25,10 +26,8 @@ auto HotkeySettings::create() -> void { auto HotkeySettings::reloadMappings() -> void { mappingList.reset(); - mappingList.append(TableViewHeader().setVisible() - .append(TableViewColumn().setText("Name")) - .append(TableViewColumn().setText("Mapping").setExpandable()) - ); + mappingList.append(TableViewColumn().setText("Name")); + mappingList.append(TableViewColumn().setText("Mapping").setExpandable()); for(auto& hotkey : inputManager.hotkeys) { mappingList.append(TableViewItem() .append(TableViewCell().setText(hotkey.name).setFont(Font().setBold()).setBackgroundColor({240, 240, 255})) diff --git a/higan/target-bsnes/settings/input.cpp b/higan/target-bsnes/settings/input.cpp index e5cc196a..993751e2 100644 --- a/higan/target-bsnes/settings/input.cpp +++ b/higan/target-bsnes/settings/input.cpp @@ -20,6 +20,7 @@ auto InputSettings::create() -> void { inputManager.turboFrequency = frequency; }); mappingList.setBatchable(); + mappingList.setHeadered(); mappingList.onActivate([&] { if(assignButton.enabled()) assignButton.doActivate(); }); mappingList.onChange([&] { updateControls(); }); assignMouse1.onActivate([&] { assignMouseInput(0); }); @@ -89,10 +90,8 @@ auto InputSettings::reloadDevices() -> void { auto InputSettings::reloadMappings() -> void { mappingList.reset(); - mappingList.append(TableViewHeader().setVisible() - .append(TableViewColumn().setText("Name")) - .append(TableViewColumn().setText("Mapping").setExpandable()) - ); + mappingList.append(TableViewColumn().setText("Name")); + mappingList.append(TableViewColumn().setText("Mapping").setExpandable()); for(auto& mapping : activeDevice().mappings) { mappingList.append(TableViewItem() .append(TableViewCell().setText(mapping.name).setFont(Font().setBold()).setBackgroundColor({240, 240, 255})) diff --git a/higan/target-bsnes/settings/settings.cpp b/higan/target-bsnes/settings/settings.cpp index a66cf0c7..2326f3e3 100644 --- a/higan/target-bsnes/settings/settings.cpp +++ b/higan/target-bsnes/settings/settings.cpp @@ -17,7 +17,7 @@ DriverSettings driverSettings; SettingsWindow settingsWindow; Settings::Settings() { - Markup::Node::operator=(BML::unserialize(string::read(locate("settings.bml")))); + Markup::Node::operator=(BML::unserialize(string::read(locate("settings.bml")), " ")); auto set = [&](string name, string value) { //create node and set to default value only if it does not already exist @@ -88,7 +88,7 @@ Settings::Settings() { } auto Settings::save() -> void { - file::write(locate("settings.bml"), BML::serialize(*this)); + file::write(locate("settings.bml"), BML::serialize(*this, " ")); } auto SettingsWindow::create() -> void { @@ -123,13 +123,14 @@ auto SettingsWindow::setVisible(bool visible) -> SettingsWindow& { if(visible) { inputSettings.refreshMappings(); hotkeySettings.refreshMappings(); + Application::processEvents(); + doSize(); } return Window::setVisible(visible), *this; } auto SettingsWindow::show(uint index) -> void { - panel.item(index)->setSelected(); + panel.item(index).setSelected(); setVisible(); setFocused(); - doSize(); } diff --git a/higan/target-bsnes/settings/settings.hpp b/higan/target-bsnes/settings/settings.hpp index 8c2e08a4..a8f4f7cd 100644 --- a/higan/target-bsnes/settings/settings.hpp +++ b/higan/target-bsnes/settings/settings.hpp @@ -155,7 +155,7 @@ public: CheckLabel autoSaveStateOnUnload{&autoStateLayout, Size{0, 0}}; CheckLabel autoLoadStateOnLoad{&autoStateLayout, Size{0, 0}}; CheckLabel suppressScreenSaver{&layout, Size{~0, 0}}; - Widget optionsSpacer{&layout, Size{~0, 10}}; + Canvas optionsSpacer{&layout, Size{~0, 1}}; Label hacksLabel{&layout, Size{~0, 0}, 2}; HorizontalLayout fastPPULayout{&layout, Size{~0, 0}}; CheckLabel fastPPUOption{&fastPPULayout, Size{0, 0}}; @@ -202,7 +202,7 @@ public: CheckLabel videoExclusiveToggle{&videoToggleLayout, Size{0, 0}}; CheckLabel videoBlockingToggle{&videoToggleLayout, Size{0, 0}}; CheckLabel videoFlushToggle{&videoToggleLayout, Size{0, 0}}; - Widget videoSpacer{&layout, Size{~0, 10}}; + Canvas videoSpacer{&layout, Size{~0, 1}}; Label audioLabel{&layout, Size{~0, 0}, 2}; TableLayout audioLayout{&layout, Size{~0, 0}}; Label audioDriverLabel{&audioLayout, Size{0, 0}}; @@ -221,7 +221,7 @@ public: CheckLabel audioExclusiveToggle{&audioToggleLayout, Size{0, 0}}; CheckLabel audioBlockingToggle{&audioToggleLayout, Size{0, 0}}; CheckLabel audioDynamicToggle{&audioToggleLayout, Size{0, 0}}; - Widget audioSpacer{&layout, Size{~0, 10}}; + Canvas audioSpacer{&layout, Size{~0, 1}}; Label inputLabel{&layout, Size{~0, 0}, 2}; TableLayout inputLayout{&layout, Size{~0, 0}}; Label inputDriverLabel{&inputLayout, Size{0, 0}}; diff --git a/higan/target-bsnes/tools/cheat-editor.cpp b/higan/target-bsnes/tools/cheat-editor.cpp index a9cf270a..3930d66a 100644 --- a/higan/target-bsnes/tools/cheat-editor.cpp +++ b/higan/target-bsnes/tools/cheat-editor.cpp @@ -55,20 +55,21 @@ auto CheatWindow::create() -> void { nameLabel.setText("Name:"); nameValue.onActivate([&] { if(acceptButton.enabled()) acceptButton.doActivate(); }); nameValue.onChange([&] { doChange(); }); + codeLayout.setAlignment(0.0); codeLabel.setText("Code:"); - codeValue.onActivate([&] { if(acceptButton.enabled()) acceptButton.doActivate(); }); + codeValue.setFont(Font().setFamily(Font::Mono)); codeValue.onChange([&] { doChange(); }); enableOption.setText("Enable"); acceptButton.onActivate([&] { doAccept(); }); cancelButton.setText("Cancel").onActivate([&] { setVisible(false); }); - setSize({400, layout.minimumSize().height()}); + setSize({400, layout.minimumSize().height() + 100}); setDismissable(); } auto CheatWindow::show(Cheat cheat) -> void { nameValue.setText(cheat.name); - codeValue.setText(cheat.code); + codeValue.setText(cheat.code.split("+").strip().merge("\n")); enableOption.setChecked(cheat.enable); doChange(); setTitle(!cheat.name ? "Add Cheat" : "Edit Cheat"); @@ -87,7 +88,7 @@ auto CheatWindow::doChange() -> void { } auto CheatWindow::doAccept() -> void { - Cheat cheat = {nameValue.text().strip(), codeValue.text().strip(), enableOption.checked()}; + Cheat cheat = {nameValue.text().strip(), codeValue.text().split("\n").strip().merge("+"), enableOption.checked()}; if(acceptButton.text() == "Add") { cheatEditor.addCheat(cheat); } else { @@ -148,10 +149,8 @@ auto CheatEditor::create() -> void { auto CheatEditor::refresh() -> void { cheatList.reset(); - cheatList.append(TableViewHeader().setVisible(false) - .append(TableViewColumn()) - .append(TableViewColumn().setExpandable()) - ); + cheatList.append(TableViewColumn()); + cheatList.append(TableViewColumn().setExpandable()); for(auto& cheat : cheats) { cheatList.append(TableViewItem() .append(TableViewCell().setCheckable().setChecked(cheat.enable)) diff --git a/higan/target-bsnes/tools/manifest-viewer.cpp b/higan/target-bsnes/tools/manifest-viewer.cpp index 70abf8d8..0b1cc165 100644 --- a/higan/target-bsnes/tools/manifest-viewer.cpp +++ b/higan/target-bsnes/tools/manifest-viewer.cpp @@ -3,18 +3,52 @@ auto ManifestViewer::create() -> void { setText("Manifest Viewer"); layout.setPadding(5); - manifestView.setEditable(false).setWordWrap(false).setFont(Font().setFamily(Font::Mono)); + manifestLabel.setText("Manifest:"); + manifestOption.onChange([&] { selectManifest(); }); + manifestSpacer.setColor({192, 192, 192}); + #if 0 && defined(Hiro_SourceEdit) + manifestView.setFont(Font().setFamily(Font::Mono).setSize(10)); + #else + manifestView.setFont(Font().setFamily(Font::Mono)); + #endif + manifestView.setEditable(false); + manifestView.setWordWrap(false); + typeIcon.setIcon(Icon::Device::Storage); + nameLabel.setText("..."); } auto ManifestViewer::loadManifest() -> void { - if(!emulator->loaded()) { - manifestView.setText(""); - verifiedIcon.setIcon({}); - verifiedLabel.setText(""); - return; - } + manifestOption.reset(); + manifestView.setText(""); + if(!emulator->loaded()) return; - manifestView.setText(emulator->manifests().merge("\n")); - verifiedIcon.setIcon(program.verified() ? Icon::Emblem::Program : Icon::Emblem::Binary); - verifiedLabel.setText(program.verified() ? "Verified" : "Unverified"); + auto manifests = emulator->manifests(); + auto titles = emulator->titles(); + for(uint offset : range(manifests.size())) { + ComboButtonItem item{&manifestOption}; + item.setProperty("manifest", manifests[offset]); + item.setText(titles[offset]); + bool verified = false; + if(offset == 0) verified = program.superFamicom.verified; + if(offset == 1 && program.gameBoy) verified = program.gameBoy.verified; + if(offset == 1 && program.bsMemory) verified = program.bsMemory.verified; + if(offset == 1 && program.sufamiTurboA) verified = program.sufamiTurboA.verified; + if(offset == 2 && program.sufamiTurboB) verified = program.sufamiTurboB.verified; + item.setIcon(verified ? Icon::Emblem::Program : Icon::Emblem::Binary); + } + manifestOption.doChange(); +} + +auto ManifestViewer::selectManifest() -> void { + auto selected = manifestOption.selected(); + uint offset = selected->offset(); + manifestView.setText(selected.property("manifest")); + string location; + if(offset == 0) location = program.superFamicom.location; + if(offset == 1 && program.gameBoy) location = program.gameBoy.location; + if(offset == 1 && program.bsMemory) location = program.bsMemory.location; + if(offset == 1 && program.sufamiTurboA) location = program.sufamiTurboA.location; + if(offset == 2 && program.sufamiTurboB) location = program.sufamiTurboB.location; + typeIcon.setIcon(location.endsWith("/") ? Icon::Action::Open : Icon::Emblem::File); + nameLabel.setText(location.trimRight("/", 1L)); } diff --git a/higan/target-bsnes/tools/state-manager.cpp b/higan/target-bsnes/tools/state-manager.cpp index 3164f72a..e2e1f8d5 100644 --- a/higan/target-bsnes/tools/state-manager.cpp +++ b/higan/target-bsnes/tools/state-manager.cpp @@ -1,62 +1,45 @@ auto StateWindow::create() -> void { layout.setPadding(5); nameLabel.setText("Name:"); - nameValue.onActivate([&] { - if(acceptButton.enabled()) acceptButton.doActivate(); - }); - nameValue.onChange([&] { - doChange(); - }); - acceptButton.onActivate([&] { - doAccept(); - }); - cancelButton.setText("Cancel").onActivate([&] { - setVisible(false); - }); + nameValue.onActivate([&] { if(acceptButton.enabled()) acceptButton.doActivate(); }); + nameValue.onChange([&] { doChange(); }); + acceptButton.onActivate([&] { doAccept(); }); + cancelButton.setText("Cancel").onActivate([&] { setVisible(false); }); setSize({400, layout.minimumSize().height()}); setDismissable(); } auto StateWindow::show(string name) -> void { - nameValue.setText(name).setProperty("input", name); + setProperty("type", {name.split("/").first(), "/"}); + setProperty("name", {name.split("/").last()}); + nameValue.setText(property("name")); doChange(); - setTitle(!name ? "Add State" : "Edit State"); + setTitle(!property("name") ? "Add State" : "Rename State"); setCentered(*toolsWindow); setVisible(); setFocused(); nameValue.setFocused(); - acceptButton.setText(!name ? "Add" : "Edit"); + acceptButton.setText(!property("name") ? "Add" : "Rename"); } auto StateWindow::doChange() -> void { - bool valid = true; auto name = nameValue.text().strip(); - if(!name) valid = false; - for(auto c : name) { - if(c == '\\' - || c == '\"' - || c == '\t' - || c == '/' - || c == ':' - || c == '*' - || c == '?' - || c == '<' - || c == '>' - || c == '|') valid = false; - } - if(auto input = nameValue.property("input")) { - if(name != input && file::exists({program.statePath(), "managed/", name, ".bst"})) valid = false; + bool valid = name && !name.contains("\\\"\t/:*?<>|"); + if(property("name") && name != property("name")) { + //don't allow a state to be renamed to the same name as an existing state (as this would overwrite it) + if(program.hasState({property("type"), name})) valid = false; } nameValue.setBackgroundColor(valid ? Color{} : Color{255, 224, 224}); acceptButton.setEnabled(valid); } auto StateWindow::doAccept() -> void { + string name = {property("type"), nameValue.text().strip()}; if(acceptButton.text() == "Add") { - stateManager.createState(nameValue.text()); + stateManager.createState(name); } else { - stateManager.modifyState(nameValue.text()); + stateManager.modifyState(name); } setVisible(false); } @@ -66,76 +49,76 @@ auto StateManager::create() -> void { setText("State Manager"); layout.setPadding(5); + stateLayout.setAlignment(0.0); stateList.setBatchable(); - stateList.onActivate([&] { - editButton.doActivate(); - }); - stateList.onChange([&] { - auto batched = stateList.batched(); - loadButton.setEnabled(batched.size() == 1); - saveButton.setEnabled(batched.size() == 1); - editButton.setEnabled(batched.size() == 1); - removeButton.setEnabled(batched.size() >= 1); + stateList.setHeadered(); + stateList.setSortable(); + stateList.onActivate([&] { loadButton.doActivate(); }); + stateList.onChange([&] { updateSelection(); }); + stateList.onSort([&](TableViewColumn column) { + column.setSorting(column.sorting() == Sort::Ascending ? Sort::Descending : Sort::Ascending); + stateList.sort(); }); + categoryLabel.setText("Category:"); + categoryOption.append(ComboButtonItem().setText("Managed States").setProperty("type", "managed/")); + categoryOption.append(ComboButtonItem().setText("Quick States").setProperty("type", "quick/")); + categoryOption.onChange([&] { loadStates(); }); + statePreviewSeparator.setColor({192, 192, 192}); + statePreviewLabel.setFont(Font().setBold()).setText("Preview"); loadButton.setText("Load").onActivate([&] { - if(auto item = stateList.selected()) { - string filename = {"managed/", item.cell(0).text()}; - program.loadState(filename); - } + if(auto item = stateList.selected()) program.loadState(item.property("name")); }); saveButton.setText("Save").onActivate([&] { - if(auto item = stateList.selected()) { - string filename = {"managed/", item.cell(0).text()}; - program.saveState(filename); - item.cell(1).setText(chrono::local::datetime(program.stateTimestamp(filename))); - } + if(auto item = stateList.selected()) program.saveState(item.property("name")); }); addButton.setText("Add").onActivate([&] { - stateWindow.show(); + stateWindow.show(type()); }); - editButton.setText("Edit").onActivate([&] { - if(auto item = stateList.selected()) { - stateWindow.show(item.cell(0).text()); - } + editButton.setText("Rename").onActivate([&] { + if(auto item = stateList.selected()) stateWindow.show(item.property("name")); }); removeButton.setText("Remove").onActivate([&] { removeStates(); }); } +auto StateManager::type() const -> string { + return categoryOption.selected().property("type"); +} + auto StateManager::loadStates() -> void { stateList.reset(); - stateList.append(TableViewHeader() - .append(TableViewColumn().setText("Name").setExpandable()) - .append(TableViewColumn().setText("Date").setForegroundColor({160, 160, 160})) - ); - for(auto& filename : program.availableStates("managed/")) { - stateList.append(TableViewItem() - .append(TableViewCell().setText(string{filename}.trimLeft("managed/", 1L))) - .append(TableViewCell().setText(chrono::local::datetime(program.stateTimestamp(filename)))) - ); + stateList.append(TableViewColumn().setText("Name").setSorting(Sort::Ascending).setExpandable()); + stateList.append(TableViewColumn().setText("Date").setForegroundColor({160, 160, 160})); + auto type = this->type(); + for(auto& state : program.availableStates(type)) { + TableViewItem item{&stateList}; + item.setProperty("name", state.name); + item.append(TableViewCell().setText(state.name.trimLeft(type, 1L))); + item.append(TableViewCell().setText(chrono::local::datetime(state.date))); } stateList.resizeColumns().doChange(); } auto StateManager::createState(string name) -> void { - program.saveState({"managed/", name}); - loadStates(); for(auto& item : stateList.items()) { - if(item.cell(0).text() == name) item.setSelected(); + item.setSelected(false); + } + program.saveState(name); + for(auto& item : stateList.items()) { + item.setSelected(item.property("name") == name); } stateList.doChange(); } auto StateManager::modifyState(string name) -> void { if(auto item = stateList.selected()) { - string from = {"managed/", item.cell(0).text()}; - string to = {"managed/", name}; + string from = item.property("name"); + string to = name; if(from != to) { program.renameState(from, to); - loadStates(); for(auto& item : stateList.items()) { - if(item.cell(0).text() == name) item.setSelected(); + item.setSelected(item.property("name") == to); } stateList.doChange(); } @@ -146,10 +129,43 @@ auto StateManager::removeStates() -> void { if(auto batched = stateList.batched()) { if(MessageDialog("Are you sure you want to permanently remove the selected state(s)?") .setParent(*toolsWindow).question() == "Yes") { - for(auto& item : batched) { - program.removeState({"managed/", item.cell(0).text()}); - } + auto lock = acquire(); + for(auto& item : batched) program.removeState(item.property("name")); loadStates(); } } } + +auto StateManager::updateSelection() -> void { + auto batched = stateList.batched(); + statePreview.setVisible(batched.size() == 1); + loadButton.setEnabled(batched.size() == 1); + saveButton.setEnabled(batched.size() == 1); + editButton.setEnabled(batched.size() == 1); + addButton.setVisible(type() != "quick/"); + editButton.setVisible(type() != "quick/"); + removeButton.setEnabled(batched.size() >= 1); + + statePreview.setColor({0, 0, 0}); + if(batched.size() == 1) { + if(auto saveState = program.loadStateData(batched.first().property("name"))) { + uint skip = memory::readl(saveState.data() + sizeof(uint)); + uint seek = 3 * sizeof(uint) + skip; + auto preview = Decode::RLE(saveState.data() + seek, max(seek, saveState.size()) - seek); + image icon{0, 15, 0x8000, 0x7c00, 0x03e0, 0x001f}; + icon.allocate(preview.data(), 256 * sizeof(uint16_t), 256, 240); + icon.transform(); + statePreview.setIcon(icon); + } + } +} + +auto StateManager::stateEvent(string name) -> void { + if(locked() || !name.beginsWith(type())) return; + auto selected = stateList.selected().property("name"); + loadStates(); + for(auto& item : stateList.items()) { + item.setSelected(item.property("name") == selected); + } + stateList.doChange(); +} diff --git a/higan/target-bsnes/tools/tools.cpp b/higan/target-bsnes/tools/tools.cpp index 427b63cf..3ab245bb 100644 --- a/higan/target-bsnes/tools/tools.cpp +++ b/higan/target-bsnes/tools/tools.cpp @@ -15,9 +15,14 @@ auto ToolsWindow::create() -> void { panel.append(cheatEditor); panel.append(stateManager); panel.append(manifestViewer); + panel.onChange([&] { + uint offset = panel.selected().offset(); + if(offset != 0) cheatDatabase.setVisible(false), cheatWindow.setVisible(false); + if(offset != 1) stateWindow.setVisible(false); + }); setTitle("Tools"); - setSize({600, 400}); + setSize({720, 480}); setAlignment({1.0, 1.0}); setDismissable(); @@ -37,6 +42,9 @@ auto ToolsWindow::setVisible(bool visible) -> ToolsWindow& { cheatDatabase.setVisible(false); cheatWindow.setVisible(false); stateWindow.setVisible(false); + } else { + Application::processEvents(); + doSize(); } return *this; } @@ -45,5 +53,4 @@ auto ToolsWindow::show(uint index) -> void { panel.item(index).setSelected(); setVisible(); setFocused(); - doSize(); } diff --git a/higan/target-bsnes/tools/tools.hpp b/higan/target-bsnes/tools/tools.hpp index 512eb06b..6602a95a 100644 --- a/higan/target-bsnes/tools/tools.hpp +++ b/higan/target-bsnes/tools/tools.hpp @@ -38,9 +38,9 @@ public: HorizontalLayout nameLayout{&layout, Size{~0, 0}}; Label nameLabel{&nameLayout, Size{40, 0}}; LineEdit nameValue{&nameLayout, Size{~0, 0}}; - HorizontalLayout codeLayout{&layout, Size{~0, 0}}; + HorizontalLayout codeLayout{&layout, Size{~0, ~0}}; Label codeLabel{&codeLayout, Size{40, 0}}; - LineEdit codeValue{&codeLayout, Size{~0, 0}}; + TextEdit codeValue{&codeLayout, Size{~0, ~0}}; HorizontalLayout controlLayout{&layout, Size{~0, 0}}; Widget spacer{&controlLayout, Size{40, 0}}; CheckLabel enableOption{&controlLayout, Size{~0, 0}}; @@ -89,16 +89,34 @@ public: Button cancelButton{&controlLayout, Size{80, 0}}; }; -struct StateManager : TabFrameItem { +struct StateManager : TabFrameItem, Lock { auto create() -> void; + auto type() const -> string; auto loadStates() -> void; auto createState(string name) -> void; auto modifyState(string name) -> void; auto removeStates() -> void; + auto updateSelection() -> void; + auto stateEvent(string name) -> void; public: + enum class SortBy : uint { + NameAscending, + NameDescending, + DateAscending, + DateDescending, + } sortBy = SortBy::NameAscending; + VerticalLayout layout{this}; - TableView stateList{&layout, Size{~0, ~0}}; + HorizontalLayout stateLayout{&layout, Size{~0, ~0}}; + TableView stateList{&stateLayout, Size{~0, ~0}}; + VerticalLayout previewLayout{&stateLayout, Size{0, ~0}}; + HorizontalLayout categoryLayout{&previewLayout, Size{~0, 0}}; + Label categoryLabel{&categoryLayout, Size{0, 0}}; + ComboButton categoryOption{&categoryLayout, Size{~0, 0}}; + Canvas statePreviewSeparator{&previewLayout, Size{~0, 1}}; + Label statePreviewLabel{&previewLayout, Size{~0, 0}}; + Canvas statePreview{&previewLayout, Size{256, 224}}; HorizontalLayout controlLayout{&layout, Size{~0, 0}}; Button loadButton{&controlLayout, Size{80, 0}}; Button saveButton{&controlLayout, Size{80, 0}}; @@ -111,13 +129,22 @@ public: struct ManifestViewer : TabFrameItem { auto create() -> void; auto loadManifest() -> void; + auto selectManifest() -> void; public: VerticalLayout layout{this}; + HorizontalLayout manifestLayout{&layout, Size{~0, 0}}; + Label manifestLabel{&manifestLayout, Size{0, 0}}; + ComboButton manifestOption{&manifestLayout, Size{~0, 0}}; + Canvas manifestSpacer{&layout, Size{~0, 1}}; + HorizontalLayout informationLayout{&layout, Size{~0, 0}}; + Canvas typeIcon{&informationLayout, Size{16, 16}}; + Label nameLabel{&informationLayout, Size{~0, 0}}; + #if 0 && defined(Hiro_SourceEdit) + SourceEdit manifestView{&layout, Size{~0, ~0}}; + #else TextEdit manifestView{&layout, Size{~0, ~0}}; - HorizontalLayout verifiedLayout{&layout, Size{~0, 0}}; - Canvas verifiedIcon{&verifiedLayout, Size{16, 16}}; - Label verifiedLabel{&verifiedLayout, Size{~0, 0}}; + #endif }; struct ToolsWindow : Window { diff --git a/hiro/core/core.cpp b/hiro/core/core.cpp index d41883ed..0930dc3f 100644 --- a/hiro/core/core.cpp +++ b/hiro/core/core.cpp @@ -26,6 +26,9 @@ using namespace nall; #define signal(function, ...) \ (delegate ? self()->function(__VA_ARGS__) : decltype(self()->function(__VA_ARGS__))()) +#define signalex(object, function, ...) \ + (object->delegate ? object->self()->function(__VA_ARGS__) : decltype(object->self()->function(__VA_ARGS__))()) + namespace hiro { #include "color.cpp" #include "gradient.cpp" @@ -90,7 +93,6 @@ namespace hiro { #include "widget/tab-frame.cpp" #include "widget/tab-frame-item.cpp" #include "widget/table-view.cpp" - #include "widget/table-view-header.cpp" #include "widget/table-view-column.cpp" #include "widget/table-view-item.cpp" #include "widget/table-view-cell.cpp" @@ -103,3 +105,4 @@ namespace hiro { } #undef signal +#undef signalex diff --git a/hiro/core/core.hpp b/hiro/core/core.hpp index fb00689e..af391a21 100644 --- a/hiro/core/core.hpp +++ b/hiro/core/core.hpp @@ -25,6 +25,7 @@ using nall::set; using nall::shared_pointer; using nall::shared_pointer_weak; using nall::string; +using nall::string_pascal; using nall::vector; namespace hiro { @@ -95,6 +96,7 @@ Declare(Viewport) enum class Orientation : uint { Horizontal, Vertical }; enum class Navigation : uint { Top, Bottom, Left, Right }; +enum class Sort : uint { None, Ascending, Descending }; #if defined(Hiro_Color) struct Color { @@ -305,40 +307,7 @@ struct Geometry { }; #endif -#if defined(Hiro_Font) -struct Font { - using type = Font; - - Font(const string& family = "", float size = 0); - - explicit operator bool() const; - auto operator==(const Font& source) const -> bool; - auto operator!=(const Font& source) const -> bool; - - auto bold() const -> bool; - auto family() const -> string; - auto italic() const -> bool; - auto reset() -> type&; - auto setBold(bool bold = true) -> type&; - auto setFamily(const string& family = "") -> type&; - auto setItalic(bool italic = true) -> type&; - auto setSize(float size = 0) -> type&; - auto size() const -> float; - auto size(const string& text) const -> Size; - - static const string Sans; - static const string Serif; - static const string Mono; - -//private: - struct State { - string family; - float size; - bool bold; - bool italic; - } state; -}; -#endif +#include "font.hpp" #if defined(Hiro_Hotkey) struct Hotkey { @@ -707,35 +676,9 @@ struct mMenuRadioItem : mAction { }; #endif -#if defined(Hiro_Sizable) -struct mSizable : mObject { - Declare(Sizable) +#include "sizable.hpp" - auto geometry() const -> Geometry; - virtual auto minimumSize() const -> Size; - virtual auto setGeometry(Geometry geometry) -> type&; - -//private: - struct State { - Geometry geometry; - } state; -}; -#endif - -#if defined(Hiro_Widget) -struct mWidget : mSizable { - Declare(Widget) - - auto doSize() const -> void; - auto onSize(const function& callback = {}) -> type&; - auto remove() -> type& override; - -//private: - struct State { - function onSize; - } state; -}; -#endif +#include "widget/widget.hpp" #if defined(Hiro_Button) struct mButton : mWidget { @@ -1274,28 +1217,7 @@ struct mRadioLabel : mWidget { }; #endif -#if defined(Hiro_SourceEdit) -struct mSourceEdit : mWidget { - Declare(SourceEdit) - - auto cursor() const -> Cursor; - auto doChange() const -> void; - auto doMove() const -> void; - auto onChange(const function& callback = {}) -> type&; - auto onMove(const function& callback = {}) -> type&; - auto setCursor(Cursor cursor = {}) -> type&; - auto setText(const string& text = "") -> type&; - auto text() const -> string; - -//private: - struct State { - Cursor cursor; - function onChange; - function onMove; - string text; - } state; -}; -#endif +#include "widget/source-edit.hpp" #if defined(Hiro_TabFrame) struct mTabFrame : mWidget { @@ -1374,210 +1296,10 @@ struct mTabFrameItem : mObject { }; #endif -#if defined(Hiro_TableView) -struct mTableView : mWidget { - Declare(TableView) - using mObject::remove; - - auto alignment() const -> Alignment; - auto append(sTableViewHeader column) -> type&; - auto append(sTableViewItem item) -> type&; - auto backgroundColor() const -> Color; - auto batchable() const -> bool; - auto batched() const -> vector; - auto bordered() const -> bool; - auto doActivate() const -> void; - auto doChange() const -> void; - auto doContext() const -> void; - auto doEdit(sTableViewCell cell) const -> void; - auto doSort(sTableViewColumn column) const -> void; - auto doToggle(sTableViewCell cell) const -> void; - auto foregroundColor() const -> Color; - auto header() const -> TableViewHeader; - auto item(uint position) const -> TableViewItem; - auto itemCount() const -> uint; - auto items() const -> vector; - auto onActivate(const function& callback = {}) -> type&; - auto onChange(const function& callback = {}) -> type&; - auto onContext(const function& callback = {}) -> type&; - auto onEdit(const function& callback = {}) -> type&; - auto onSort(const function& callback = {}) -> type&; - auto onToggle(const function& callback = {}) -> type&; - auto remove(sTableViewHeader column) -> type&; - auto remove(sTableViewItem item) -> type&; - auto reset() -> type&; - auto resizeColumns() -> type&; - auto selected() const -> TableViewItem; - auto setAlignment(Alignment alignment = {}) -> type&; - auto setBackgroundColor(Color color = {}) -> type&; - auto setBatchable(bool batchable = true) -> type&; - auto setBordered(bool bordered = true) -> type&; - auto setForegroundColor(Color color = {}) -> type&; - auto setParent(mObject* parent = nullptr, int offset = -1) -> type& override; - -//private: - struct State { - uint activeColumn = 0; - Alignment alignment; - Color backgroundColor; - bool batchable = false; - bool bordered = false; - Color foregroundColor; - sTableViewHeader header; - vector items; - function onActivate; - function onChange; - function onContext; - function onEdit; - function onSort; - function onToggle; - } state; - - auto destruct() -> void override; -}; -#endif - -#if defined(Hiro_TableView) -struct mTableViewHeader : mObject { - Declare(TableViewHeader) - - auto append(sTableViewColumn column) -> type&; - auto column(uint position) const -> TableViewColumn; - auto columnCount() const -> uint; - auto columns() const -> vector; - auto remove() -> type& override; - auto remove(sTableViewColumn column) -> type&; - auto reset() -> type&; - auto setParent(mObject* parent = nullptr, int offset = -1) -> type& override; - -//private: - struct State { - vector columns; - } state; - - auto destruct() -> void override; -}; -#endif - -#if defined(Hiro_TableView) -struct mTableViewColumn : mObject { - Declare(TableViewColumn) - - auto active() const -> bool; - auto alignment() const -> Alignment; - auto backgroundColor() const -> Color; - auto editable() const -> bool; - auto expandable() const -> bool; - auto foregroundColor() const -> Color; - auto horizontalAlignment() const -> float; - auto icon() const -> image; - auto remove() -> type& override; - auto resizable() const -> bool; - auto setActive() -> type&; - auto setAlignment(Alignment alignment = {}) -> type&; - auto setBackgroundColor(Color color = {}) -> type&; - auto setEditable(bool editable = true) -> type&; - auto setExpandable(bool expandable = true) -> type&; - auto setForegroundColor(Color color = {}) -> type&; - auto setHorizontalAlignment(float alignment = 0.0) -> type&; - auto setIcon(const image& icon = {}) -> type&; - auto setResizable(bool resizable = true) -> type&; - auto setSortable(bool sortable = true) -> type&; - auto setText(const string& text = "") -> type&; - auto setVerticalAlignment(float alignment = 0.5) -> type&; - auto setVisible(bool visible = true) -> type&; - auto setWidth(float width = 0) -> type&; - auto sortable() const -> bool; - auto text() const -> string; - auto verticalAlignment() const -> float; - auto width() const -> float; - -//private: - struct State { - Alignment alignment; - Color backgroundColor; - bool editable = false; - bool expandable = false; - Color foregroundColor; - float horizontalAlignment = 0.0; - image icon; - bool resizable = true; - bool sortable = false; - string text; - float verticalAlignment = 0.5; - bool visible = true; - float width = 0; - } state; -}; -#endif - -#if defined(Hiro_TableView) -struct mTableViewItem : mObject { - Declare(TableViewItem) - - auto alignment() const -> Alignment; - auto append(sTableViewCell cell) -> type&; - auto backgroundColor() const -> Color; - auto cell(uint position) const -> TableViewCell; - auto cellCount() const -> uint; - auto cells() const -> vector; - auto foregroundColor() const -> Color; - auto remove() -> type& override; - auto remove(sTableViewCell cell) -> type&; - auto reset() -> type&; - auto selected() const -> bool; - auto setAlignment(Alignment alignment = {}) -> type&; - auto setBackgroundColor(Color color = {}) -> type&; - auto setFocused() -> type& override; - auto setForegroundColor(Color color = {}) -> type&; - auto setParent(mObject* parent = nullptr, int offset = -1) -> type& override; - auto setSelected(bool selected = true) -> type&; - -//private: - struct State { - Alignment alignment; - Color backgroundColor; - vector cells; - Color foregroundColor; - bool selected = false; - } state; - - auto destruct() -> void override; -}; -#endif - -#if defined(Hiro_TableView) -struct mTableViewCell : mObject { - Declare(TableViewCell) - - auto alignment(bool recursive = false) const -> Alignment; - auto backgroundColor(bool recursive = false) const -> Color; - auto checkable() const -> bool; - auto checked() const -> bool; - auto font(bool recursive = false) const -> Font; - auto foregroundColor(bool recursive = false) const -> Color; - auto icon() const -> image; - auto setAlignment(Alignment alignment = {}) -> type&; - auto setBackgroundColor(Color color = {}) -> type&; - auto setCheckable(bool checkable = true) -> type&; - auto setChecked(bool checked = true) -> type&; - auto setForegroundColor(Color color = {}) -> type&; - auto setIcon(const image& icon = {}) -> type&; - auto setText(const string& text = "") -> type&; - auto text() const -> string; - -//private: - struct State { - Alignment alignment; - Color backgroundColor; - bool checkable = false; - bool checked = false; - Color foregroundColor; - image icon; - string text; - } state; -}; -#endif +#include "widget/table-view.hpp" +#include "widget/table-view-column.hpp" +#include "widget/table-view-item.hpp" +#include "widget/table-view-cell.hpp" #if defined(Hiro_TextEdit) struct mTextEdit : mWidget { diff --git a/hiro/core/font.hpp b/hiro/core/font.hpp new file mode 100644 index 00000000..492a5424 --- /dev/null +++ b/hiro/core/font.hpp @@ -0,0 +1,35 @@ +#if defined(Hiro_Font) +struct Font { + using type = Font; + + Font(const string& family = "", float size = 0); + + explicit operator bool() const; + auto operator==(const Font& source) const -> bool; + auto operator!=(const Font& source) const -> bool; + + auto bold() const -> bool; + auto family() const -> string; + auto italic() const -> bool; + auto reset() -> type&; + auto setBold(bool bold = true) -> type&; + auto setFamily(const string& family = "") -> type&; + auto setItalic(bool italic = true) -> type&; + auto setSize(float size = 0) -> type&; + auto size() const -> float; + auto size(const string& text) const -> Size; + + static const string Sans; + static const string Serif; + static const string Mono; + +//private: +//sizeof(Font) == 32 + struct State { + string family; //24 + float size = 8.0; //4 + char bold = false; //1+ + char italic = false; //1=4 + } state; +}; +#endif diff --git a/hiro/core/lock.hpp b/hiro/core/lock.hpp index e198e3d6..fb226003 100644 --- a/hiro/core/lock.hpp +++ b/hiro/core/lock.hpp @@ -50,3 +50,5 @@ struct mLock { mutable int locks = 0; }; + +using Lock = mLock; diff --git a/hiro/core/object.cpp b/hiro/core/object.cpp index a29e2de1..dc24d079 100644 --- a/hiro/core/object.cpp +++ b/hiro/core/object.cpp @@ -199,16 +199,6 @@ auto mObject::parentTableView(bool recursive) const -> mTableView* { } #endif -#if defined(Hiro_TableView) -auto mObject::parentTableViewHeader(bool recursive) const -> mTableViewHeader* { - if(auto tableViewHeader = dynamic_cast(parent())) return tableViewHeader; - if(recursive) { - if(auto object = parent()) return object->parentTableViewHeader(true); - } - return nullptr; -} -#endif - #if defined(Hiro_TableView) auto mObject::parentTableViewItem(bool recursive) const -> mTableViewItem* { if(auto tableViewItem = dynamic_cast(parent())) return tableViewItem; diff --git a/hiro/core/object.hpp b/hiro/core/object.hpp index 9a3f4b64..788255be 100644 --- a/hiro/core/object.hpp +++ b/hiro/core/object.hpp @@ -28,7 +28,6 @@ struct mObject { auto parentTabFrame(bool recursive = false) const -> mTabFrame*; auto parentTabFrameItem(bool recursive = false) const -> mTabFrameItem*; auto parentTableView(bool recursive = false) const -> mTableView*; - auto parentTableViewHeader(bool recursive = false) const -> mTableViewHeader*; auto parentTableViewItem(bool recursive = false) const -> mTableViewItem*; auto parentTreeView(bool recursive = false) const -> mTreeView*; auto parentTreeViewItem(bool recursive = false) const -> mTreeViewItem*; @@ -47,17 +46,19 @@ struct mObject { auto visible(bool recursive = false) const -> bool; //private: +//sizeof(mObject) == 72 struct State { - bool enabled = true; - Font font; - int offset = -1; - mObject* parent = nullptr; - set properties; - bool visible = true; + Font font; //16 + set properties; //16 + mObject* parent = nullptr; // 8 + int offset = -1; // 4 + char enabled = true; // 1+ + char visible = true; // 1=4 } state; - wObject instance; - pObject* delegate = nullptr; + wObject instance; // 8 + pObject* delegate = nullptr; // 8 + //vtable // 8 virtual auto construct() -> void; virtual auto destruct() -> void; diff --git a/hiro/core/shared.hpp b/hiro/core/shared.hpp index d918ba25..46042a55 100644 --- a/hiro/core/shared.hpp +++ b/hiro/core/shared.hpp @@ -1,5 +1,6 @@ #define DeclareShared(Name) \ using type = Name; \ + using internalType = m##Name; \ Name() : s##Name(new m##Name, [](auto p) { \ p->unbind(); \ delete p; \ @@ -7,6 +8,9 @@ (*this)->bind(*this); \ } \ Name(const s##Name& source) : s##Name(source) { assert(source); } \ + template Name(const T& source, \ + std::enable_if_t::value>* = 0) : \ + s##Name((s##Name&)source) { assert(source); } \ explicit operator bool() const { return self().operator bool(); } \ auto self() const -> m##Name& { return (m##Name&)operator*(); } \ @@ -15,6 +19,9 @@ template Name(T* parent, P&&... p) : Name() { \ if(parent) (*parent)->append(*this, std::forward

(p)...); \ } \ + template auto is() -> bool { \ + return dynamic_cast(data()); \ + } \ template auto cast() -> T { \ if(auto pointer = dynamic_cast(data())) { \ if(auto shared = pointer->instance.acquire()) return T(shared); \ @@ -57,14 +64,12 @@ #if defined(Hiro_Object) struct Object : sObject { DeclareSharedObject(Object) - using internalType = mObject; }; #endif #if defined(Hiro_Group) struct Group : sGroup { DeclareShared(Group) - using internalType = mGroup; template Group(P&&... p) : Group() { _append(std::forward

(p)...); } auto append(sObject object) -> type& { return self().append(object), *this; } @@ -93,7 +98,6 @@ private: #if defined(Hiro_Timer) struct Timer : sTimer { DeclareSharedObject(Timer) - using internalType = mTimer; auto doActivate() const { return self().doActivate(); } auto interval() const { return self().interval(); } @@ -105,14 +109,12 @@ struct Timer : sTimer { #if defined(Hiro_Action) struct Action : sAction { DeclareSharedAction(Action) - using internalType = mAction; }; #endif #if defined(Hiro_Menu) struct Menu : sMenu { DeclareSharedAction(Menu) - using internalType = mMenu; auto action(unsigned position) const { return self().action(position); } auto actionCount() const { return self().actionCount(); } @@ -130,14 +132,12 @@ struct Menu : sMenu { #if defined(Hiro_MenuSeparator) struct MenuSeparator : sMenuSeparator { DeclareSharedAction(MenuSeparator) - using internalType = mMenuSeparator; }; #endif #if defined(Hiro_MenuItem) struct MenuItem : sMenuItem { DeclareSharedAction(MenuItem) - using internalType = mMenuItem; auto doActivate() const { return self().doActivate(); } auto icon() const { return self().icon(); } @@ -151,7 +151,6 @@ struct MenuItem : sMenuItem { #if defined(Hiro_MenuCheckItem) struct MenuCheckItem : sMenuCheckItem { DeclareSharedAction(MenuCheckItem) - using internalType = mMenuCheckItem; auto checked() const { return self().checked(); } auto doToggle() const { return self().doToggle(); } @@ -165,7 +164,6 @@ struct MenuCheckItem : sMenuCheckItem { #if defined(Hiro_MenuRadioItem) struct MenuRadioItem : sMenuRadioItem { DeclareSharedAction(MenuRadioItem) - using internalType = mMenuRadioItem; auto checked() const { return self().checked(); } auto doActivate() const { return self().doActivate(); } @@ -180,21 +178,18 @@ struct MenuRadioItem : sMenuRadioItem { #if defined(Hiro_Sizable) struct Sizable : sSizable { DeclareSharedSizable(Sizable) - using internalType = mSizable; }; #endif #if defined(Hiro_Widget) struct Widget : sWidget { DeclareSharedWidget(Widget) - using internalType = mWidget; }; #endif #if defined(Hiro_Button) struct Button : sButton { DeclareSharedWidget(Button) - using internalType = mButton; auto bordered() const { return self().bordered(); } auto doActivate() const { return self().doActivate(); } @@ -212,7 +207,6 @@ struct Button : sButton { #if defined(Hiro_Canvas) struct Canvas : sCanvas { DeclareSharedWidget(Canvas) - using internalType = mCanvas; auto color() const { return self().color(); } auto data() { return self().data(); } @@ -241,7 +235,6 @@ struct Canvas : sCanvas { #if defined(Hiro_CheckButton) struct CheckButton : sCheckButton { DeclareSharedWidget(CheckButton) - using internalType = mCheckButton; auto bordered() const { return self().bordered(); } auto checked() const { return self().checked(); } @@ -261,7 +254,6 @@ struct CheckButton : sCheckButton { #if defined(Hiro_CheckLabel) struct CheckLabel : sCheckLabel { DeclareSharedWidget(CheckLabel) - using internalType = mCheckLabel; auto checked() const { return self().checked(); } auto doToggle() const { return self().doToggle(); } @@ -275,7 +267,6 @@ struct CheckLabel : sCheckLabel { #if defined(Hiro_ComboButton) struct ComboButtonItem : sComboButtonItem { DeclareSharedObject(ComboButtonItem) - using internalType = mComboButtonItem; auto icon() const { return self().icon(); } auto selected() const { return self().selected(); } @@ -289,7 +280,6 @@ struct ComboButtonItem : sComboButtonItem { #if defined(Hiro_ComboButton) struct ComboButton : sComboButton { DeclareSharedWidget(ComboButton) - using internalType = mComboButton; auto append(sComboButtonItem item) { return self().append(item), *this; } auto doChange() const { return self().doChange(); } @@ -307,7 +297,6 @@ struct ComboButton : sComboButton { #if defined(Hiro_ComboEdit) struct ComboEditItem : sComboEditItem { DeclareSharedObject(ComboEditItem) - using internalType = mComboEditItem; auto icon() const { return self().icon(); } auto setIcon(const image& icon = {}) { return self().setIcon(icon), *this; } @@ -319,7 +308,6 @@ struct ComboEditItem : sComboEditItem { #if defined(Hiro_ComboEdit) struct ComboEdit : sComboEdit { DeclareSharedWidget(ComboEdit) - using internalType = mComboEdit; auto append(sComboEditItem item) { return self().append(item), *this; } auto backgroundColor() const { return self().backgroundColor(); } @@ -345,7 +333,6 @@ struct ComboEdit : sComboEdit { #if defined(Hiro_Console) struct Console : sConsole { DeclareSharedWidget(Console) - using internalType = mConsole; auto backgroundColor() const { return self().backgroundColor(); } auto doActivate(string command) const { return self().doActivate(command); } @@ -363,7 +350,6 @@ struct Console : sConsole { #if defined(Hiro_Frame) struct Frame : sFrame { DeclareSharedWidget(Frame) - using internalType = mFrame; auto append(sSizable sizable) { return self().append(sizable), *this; } auto remove(sSizable sizable) { return self().remove(sizable), *this; } @@ -377,7 +363,6 @@ struct Frame : sFrame { #if defined(Hiro_HexEdit) struct HexEdit : sHexEdit { DeclareSharedWidget(HexEdit) - using internalType = mHexEdit; auto address() const { return self().address(); } auto backgroundColor() const { return self().backgroundColor(); } @@ -402,7 +387,6 @@ struct HexEdit : sHexEdit { #if defined(Hiro_HorizontalScrollBar) struct HorizontalScrollBar : sHorizontalScrollBar { DeclareSharedWidget(HorizontalScrollBar) - using internalType = mHorizontalScrollBar; auto doChange() const { return self().doChange(); } auto length() const { return self().length(); } @@ -416,7 +400,6 @@ struct HorizontalScrollBar : sHorizontalScrollBar { #if defined(Hiro_HorizontalSlider) struct HorizontalSlider : sHorizontalSlider { DeclareSharedWidget(HorizontalSlider) - using internalType = mHorizontalSlider; auto doChange() const { return self().doChange(); } auto length() const { return self().length(); } @@ -430,7 +413,6 @@ struct HorizontalSlider : sHorizontalSlider { #if defined(Hiro_IconView) struct IconViewItem : sIconViewItem { DeclareSharedObject(IconViewItem) - using internalType = mIconViewItem; auto icon() const { return self().icon(); } auto selected() const { return self().selected(); } @@ -444,7 +426,6 @@ struct IconViewItem : sIconViewItem { #if defined(Hiro_IconView) struct IconView : sIconView { DeclareSharedWidget(IconView) - using internalType = mIconView; auto append(sIconViewItem item) { return self().append(item), *this; } auto backgroundColor() const { return self().backgroundColor(); } @@ -477,7 +458,6 @@ struct IconView : sIconView { #if defined(Hiro_Label) struct Label : sLabel { DeclareSharedWidget(Label) - using internalType = mLabel; auto alignment() const { return self().alignment(); } auto backgroundColor() const { return self().backgroundColor(); } @@ -493,7 +473,6 @@ struct Label : sLabel { #if defined(Hiro_LineEdit) struct LineEdit : sLineEdit { DeclareSharedWidget(LineEdit) - using internalType = mLineEdit; auto backgroundColor() const { return self().backgroundColor(); } auto doActivate() const { return self().doActivate(); } @@ -513,7 +492,6 @@ struct LineEdit : sLineEdit { #if defined(Hiro_ProgressBar) struct ProgressBar : sProgressBar { DeclareSharedWidget(ProgressBar) - using internalType = mProgressBar; auto position() const { return self().position(); } auto setPosition(unsigned position = 0) { return self().setPosition(position), *this; } @@ -523,7 +501,6 @@ struct ProgressBar : sProgressBar { #if defined(Hiro_RadioButton) struct RadioButton : sRadioButton { DeclareSharedWidget(RadioButton) - using internalType = mRadioButton; auto bordered() const { return self().bordered(); } auto checked() const { return self().checked(); } @@ -544,7 +521,6 @@ struct RadioButton : sRadioButton { #if defined(Hiro_RadioLabel) struct RadioLabel : sRadioLabel { DeclareSharedWidget(RadioLabel) - using internalType = mRadioLabel; auto checked() const { return self().checked(); } auto doActivate() const { return self().doActivate(); } @@ -559,23 +535,25 @@ struct RadioLabel : sRadioLabel { #if defined(Hiro_SourceEdit) struct SourceEdit : sSourceEdit { DeclareSharedWidget(SourceEdit) - using internalType = mSourceEdit; auto cursor() const { return self().cursor(); } auto doChange() const { return self().doChange(); } auto doMove() const { return self().doMove(); } + auto editable() const { return self().editable(); } auto onChange(const function& callback = {}) { return self().onChange(callback), *this; } auto onMove(const function& callback = {}) { return self().onMove(callback), *this; } auto setCursor(Cursor cursor = {}) { return self().setCursor(cursor), *this; } + auto setEditable(bool editable = true) { return self().setEditable(editable), *this; } auto setText(const string& text = "") { return self().setText(text), *this; } + auto setWordWrap(bool wordWrap = true) { return self().setWordWrap(wordWrap), *this; } auto text() const { return self().text(); } + auto wordWrap() const { return self().wordWrap(); } }; #endif #if defined(Hiro_TabFrame) struct TabFrameItem : sTabFrameItem { DeclareSharedObject(TabFrameItem) - using internalType = mTabFrameItem; auto append(sSizable sizable) { return self().append(sizable), *this; } auto closable() const { return self().closable(); } @@ -597,7 +575,6 @@ struct TabFrameItem : sTabFrameItem { #if defined(Hiro_TabFrame) struct TabFrame : sTabFrame { DeclareSharedWidget(TabFrame) - using internalType = mTabFrame; auto append(sTabFrameItem item) { return self().append(item), *this; } auto doChange() const { return self().doChange(); } @@ -620,7 +597,6 @@ struct TabFrame : sTabFrame { #if defined(Hiro_TableView) struct TableViewColumn : sTableViewColumn { DeclareSharedObject(TableViewColumn) - using internalType = mTableViewColumn; auto active() const { return self().active(); } auto alignment() const { return self().alignment(); } @@ -640,35 +616,21 @@ struct TableViewColumn : sTableViewColumn { auto setHorizontalAlignment(float alignment = 0.0) { return self().setHorizontalAlignment(alignment), *this; } auto setIcon(const image& icon = {}) { return self().setIcon(icon), *this; } auto setResizable(bool resizable = true) { return self().setResizable(resizable), *this; } - auto setSortable(bool sortable = true) { return self().setSortable(sortable), *this; } + auto setSorting(Sort sorting = Sort::None) { return self().setSorting(sorting), *this; } auto setText(const string& text = "") { return self().setText(text), *this; } auto setVerticalAlignment(float alignment = 0.5) { return self().setVerticalAlignment(alignment), *this; } auto setWidth(float width = 0) { return self().setWidth(width), *this; } - auto sortable() const { return self().sortable(); } + auto sort(Sort sorting) { return self().sort(sorting), *this; } + auto sorting() const { return self().sorting(); } auto text() const { return self().text(); } auto verticalAlignment() const { return self().verticalAlignment(); } auto width() const { return self().width(); } }; #endif -#if defined(Hiro_TableView) -struct TableViewHeader : sTableViewHeader { - DeclareSharedObject(TableViewHeader) - using internalType = mTableViewHeader; - - auto append(sTableViewColumn column) { return self().append(column), *this; } - auto column(unsigned position) const { return self().column(position); } - auto columnCount() const { return self().columnCount(); } - auto columns() const { return self().columns(); } - auto remove(sTableViewColumn column) { return self().remove(column), *this; } - auto reset() { return self().reset(), *this; } -}; -#endif - #if defined(Hiro_TableView) struct TableViewCell : sTableViewCell { DeclareSharedObject(TableViewCell) - using internalType = mTableViewCell; auto alignment() const { return self().alignment(); } auto backgroundColor() const { return self().backgroundColor(); } @@ -690,7 +652,6 @@ struct TableViewCell : sTableViewCell { #if defined(Hiro_TableView) struct TableViewItem : sTableViewItem { DeclareSharedObject(TableViewItem) - using internalType = mTableViewItem; auto alignment() const { return self().alignment(); } auto append(sTableViewCell cell) { return self().append(cell), *this; } @@ -712,15 +673,17 @@ struct TableViewItem : sTableViewItem { #if defined(Hiro_TableView) struct TableView : sTableView { DeclareSharedWidget(TableView) - using internalType = mTableView; auto alignment() const { return self().alignment(); } - auto append(sTableViewHeader header) { return self().append(header), *this; } + auto append(sTableViewColumn column) { return self().append(column), *this; } auto append(sTableViewItem item) { return self().append(item), *this; } auto backgroundColor() const { return self().backgroundColor(); } auto batchable() const { return self().batchable(); } auto batched() const { return self().batched(); } auto bordered() const { return self().bordered(); } + auto column(uint position) const { return self().column(position); } + auto columnCount() const { return self().columnCount(); } + auto columns() const { return self().columns(); } auto doActivate() const { return self().doActivate(); } auto doChange() const { return self().doChange(); } auto doContext() const { return self().doContext(); } @@ -728,7 +691,7 @@ struct TableView : sTableView { auto doSort(sTableViewColumn column) const { return self().doSort(column); } auto doToggle(sTableViewCell cell) const { return self().doToggle(cell); } auto foregroundColor() const { return self().foregroundColor(); } - auto header() const { return self().header(); } + auto headered() const { return self().headered(); } auto item(unsigned position) const { return self().item(position); } auto itemCount() const { return self().itemCount(); } auto items() const { return self().items(); } @@ -738,7 +701,7 @@ struct TableView : sTableView { auto onEdit(const function& callback = {}) { return self().onEdit(callback), *this; } auto onSort(const function& callback = {}) { return self().onSort(callback), *this; } auto onToggle(const function& callback = {}) { return self().onToggle(callback), *this; } - auto remove(sTableViewHeader header) { return self().remove(header), *this; } + auto remove(sTableViewColumn column) { return self().remove(column), *this; } auto remove(sTableViewItem item) { return self().remove(item), *this; } auto reset() { return self().reset(), *this; } auto resizeColumns() { return self().resizeColumns(), *this; } @@ -748,13 +711,16 @@ struct TableView : sTableView { auto setBatchable(bool batchable = true) { return self().setBatchable(batchable), *this; } auto setBordered(bool bordered = true) { return self().setBordered(bordered), *this; } auto setForegroundColor(Color color = {}) { return self().setForegroundColor(color), *this; } + auto setHeadered(bool headered = true) { return self().setHeadered(headered), *this; } + auto setSortable(bool sortable = true) { return self().setSortable(sortable), *this; } + auto sort() { return self().sort(), *this; } + auto sortable() const { return self().sortable(); } }; #endif #if defined(Hiro_TextEdit) struct TextEdit : sTextEdit { DeclareSharedWidget(TextEdit) - using internalType = mTextEdit; auto backgroundColor() const { return self().backgroundColor(); } auto cursor() const { return self().cursor(); } @@ -778,7 +744,6 @@ struct TextEdit : sTextEdit { #if defined(Hiro_TreeView) struct TreeViewItem : sTreeViewItem { DeclareSharedObject(TreeViewItem) - using internalType = mTreeViewItem; auto append(sTreeViewItem item) { return self().append(item), *this; } auto backgroundColor() const { return self().backgroundColor(); } @@ -807,7 +772,6 @@ struct TreeViewItem : sTreeViewItem { #if defined(Hiro_TreeView) struct TreeView : sTreeView { DeclareSharedWidget(TreeView) - using internalType = mTreeView; auto append(sTreeViewItem item) { return self().append(item), *this; } auto backgroundColor() const { return self().backgroundColor(); } @@ -834,7 +798,6 @@ struct TreeView : sTreeView { #if defined(Hiro_VerticalScrollBar) struct VerticalScrollBar : sVerticalScrollBar { DeclareSharedWidget(VerticalScrollBar) - using internalType = mVerticalScrollBar; auto doChange() const { return self().doChange(); } auto length() const { return self().length(); } @@ -848,7 +811,6 @@ struct VerticalScrollBar : sVerticalScrollBar { #if defined(Hiro_VerticalSlider) struct VerticalSlider : sVerticalSlider { DeclareSharedWidget(VerticalSlider) - using internalType = mVerticalSlider; auto doChange() const { return self().doChange(); } auto length() const { return self().length(); } @@ -862,7 +824,6 @@ struct VerticalSlider : sVerticalSlider { #if defined(Hiro_Viewport) struct Viewport : sViewport { DeclareSharedWidget(Viewport) - using internalType = mViewport; auto doDrop(vector names) const { return self().doDrop(names); } auto doMouseLeave() const { return self().doMouseLeave(); } @@ -883,7 +844,6 @@ struct Viewport : sViewport { #if defined(Hiro_StatusBar) struct StatusBar : sStatusBar { DeclareSharedObject(StatusBar) - using internalType = mStatusBar; auto setText(const string& text = "") { return self().setText(text), *this; } auto text() const { return self().text(); } @@ -893,7 +853,6 @@ struct StatusBar : sStatusBar { #if defined(Hiro_PopupMenu) struct PopupMenu : sPopupMenu { DeclareSharedObject(PopupMenu) - using internalType = mPopupMenu; auto action(unsigned position) const { return self().action(position); } auto actionCount() const { return self().actionCount(); } @@ -907,7 +866,6 @@ struct PopupMenu : sPopupMenu { #if defined(Hiro_MenuBar) struct MenuBar : sMenuBar { DeclareSharedObject(MenuBar) - using internalType = mMenuBar; auto append(sMenu menu) { return self().append(menu), *this; } auto menu(unsigned position) const { return self().menu(position); } @@ -921,7 +879,6 @@ struct MenuBar : sMenuBar { #if defined(Hiro_Window) struct Window : sWindow { DeclareSharedObject(Window) - using internalType = mWindow; auto append(sMenuBar menuBar) { return self().append(menuBar), *this; } auto append(sSizable sizable) { return self().append(sizable), *this; } diff --git a/hiro/core/sizable.hpp b/hiro/core/sizable.hpp new file mode 100644 index 00000000..7cecda79 --- /dev/null +++ b/hiro/core/sizable.hpp @@ -0,0 +1,15 @@ +#if defined(Hiro_Sizable) +struct mSizable : mObject { + Declare(Sizable) + + auto geometry() const -> Geometry; + virtual auto minimumSize() const -> Size; + virtual auto setGeometry(Geometry geometry) -> type&; + +//private: +//sizeof(mSizable) == 16 + struct State { + Geometry geometry; + } state; +}; +#endif diff --git a/hiro/core/widget/canvas.cpp b/hiro/core/widget/canvas.cpp index bbde8dd9..19b224e0 100644 --- a/hiro/core/widget/canvas.cpp +++ b/hiro/core/widget/canvas.cpp @@ -73,6 +73,8 @@ auto mCanvas::onMouseRelease(const function& callback) -> auto mCanvas::setColor(Color color) -> type& { state.color = color; + state.gradient = {}; + state.icon = {}; signal(setColor, color); return *this; } @@ -84,12 +86,16 @@ auto mCanvas::setDroppable(bool droppable) -> type& { } auto mCanvas::setGradient(Gradient gradient) -> type& { + state.color = {}; state.gradient = gradient; + state.icon = {}; signal(setGradient, gradient); return *this; } auto mCanvas::setIcon(const image& icon) -> type& { + state.color = {}; + state.gradient = {}; state.icon = icon; signal(setIcon, icon); return *this; diff --git a/hiro/core/widget/source-edit.cpp b/hiro/core/widget/source-edit.cpp index 5ef494a5..855960e3 100644 --- a/hiro/core/widget/source-edit.cpp +++ b/hiro/core/widget/source-edit.cpp @@ -18,6 +18,10 @@ auto mSourceEdit::doMove() const -> void { if(state.onMove) return state.onMove(); } +auto mSourceEdit::editable() const -> bool { + return state.editable; +} + auto mSourceEdit::onChange(const function& callback) -> type& { state.onChange = callback; return *this; @@ -34,14 +38,30 @@ auto mSourceEdit::setCursor(Cursor cursor) -> type& { return *this; } +auto mSourceEdit::setEditable(bool editable) -> type& { + state.editable = editable; + signal(setEditable, editable); + return *this; +} + auto mSourceEdit::setText(const string& text) -> type& { state.text = text; signal(setText, text); return *this; } +auto mSourceEdit::setWordWrap(bool wordWrap) -> type& { + state.wordWrap = wordWrap; + signal(setWordWrap, wordWrap); + return *this; +} + auto mSourceEdit::text() const -> string { return signal(text); } +auto mSourceEdit::wordWrap() const -> bool { + return state.wordWrap; +} + #endif diff --git a/hiro/core/widget/source-edit.hpp b/hiro/core/widget/source-edit.hpp new file mode 100644 index 00000000..91583936 --- /dev/null +++ b/hiro/core/widget/source-edit.hpp @@ -0,0 +1,28 @@ +#if defined(Hiro_SourceEdit) +struct mSourceEdit : mWidget { + Declare(SourceEdit) + + auto cursor() const -> Cursor; + auto doChange() const -> void; + auto doMove() const -> void; + auto editable() const -> bool; + auto onChange(const function& callback = {}) -> type&; + auto onMove(const function& callback = {}) -> type&; + auto setCursor(Cursor cursor = {}) -> type&; + auto setEditable(bool editable) -> type&; + auto setText(const string& text = "") -> type&; + auto setWordWrap(bool wordWrap = true) -> type&; + auto text() const -> string; + auto wordWrap() const -> bool; + +//private: + struct State { + Cursor cursor; + bool editable = true; + function onChange; + function onMove; + string text; + bool wordWrap = true; + } state; +}; +#endif diff --git a/hiro/core/widget/table-view-cell.cpp b/hiro/core/widget/table-view-cell.cpp index aee788f3..bb81f131 100644 --- a/hiro/core/widget/table-view-cell.cpp +++ b/hiro/core/widget/table-view-cell.cpp @@ -12,11 +12,9 @@ auto mTableViewCell::alignment(bool recursive) const -> Alignment { if(auto parent = parentTableViewItem()) { if(auto alignment = parent->state.alignment) return alignment; if(auto grandparent = parent->parentTableView()) { - if(auto header = grandparent->state.header) { - if(offset() < header->columnCount()) { - if(auto column = header->state.columns[offset()]) { - if(auto alignment = column->state.alignment) return alignment; - } + if(offset() < grandparent->columnCount()) { + if(auto column = grandparent->state.columns[offset()]) { + if(auto alignment = column->state.alignment) return alignment; } } if(auto alignment = grandparent->state.alignment) return alignment; @@ -32,11 +30,9 @@ auto mTableViewCell::backgroundColor(bool recursive) const -> Color { if(auto parent = parentTableViewItem()) { if(auto color = parent->state.backgroundColor) return color; if(auto grandparent = parent->parentTableView()) { - if(auto header = grandparent->state.header) { - if(offset() < header->columnCount()) { - if(auto column = header->state.columns[offset()]) { - if(auto color = column->state.backgroundColor) return color; - } + if(offset() < grandparent->columnCount()) { + if(auto column = grandparent->state.columns[offset()]) { + if(auto color = column->state.backgroundColor) return color; } } if(auto color = grandparent->state.backgroundColor) return color; @@ -60,11 +56,9 @@ auto mTableViewCell::font(bool recursive) const -> Font { if(auto parent = parentTableViewItem()) { if(auto font = parent->font()) return font; if(auto grandparent = parent->parentTableView()) { - if(auto header = grandparent->state.header) { - if(offset() < header->columnCount()) { - if(auto column = header->state.columns[offset()]) { - if(auto font = column->font()) return font; - } + if(offset() < grandparent->columnCount()) { + if(auto column = grandparent->state.columns[offset()]) { + if(auto font = column->font()) return font; } } if(auto font = grandparent->font(true)) return font; @@ -80,11 +74,9 @@ auto mTableViewCell::foregroundColor(bool recursive) const -> Color { if(auto parent = parentTableViewItem()) { if(auto color = parent->state.foregroundColor) return color; if(auto grandparent = parent->parentTableView()) { - if(auto header = grandparent->state.header) { - if(offset() < header->columnCount()) { - if(auto column = header->state.columns[offset()]) { - if(auto color = column->state.foregroundColor) return color; - } + if(offset() < grandparent->columnCount()) { + if(auto column = grandparent->state.columns[offset()]) { + if(auto color = column->state.foregroundColor) return color; } } if(auto color = grandparent->state.foregroundColor) return color; diff --git a/hiro/core/widget/table-view-cell.hpp b/hiro/core/widget/table-view-cell.hpp new file mode 100644 index 00000000..c996ac56 --- /dev/null +++ b/hiro/core/widget/table-view-cell.hpp @@ -0,0 +1,32 @@ +#if defined(Hiro_TableView) +struct mTableViewCell : mObject { + Declare(TableViewCell) + + auto alignment(bool recursive = false) const -> Alignment; + auto backgroundColor(bool recursive = false) const -> Color; + auto checkable() const -> bool; + auto checked() const -> bool; + auto font(bool recursive = false) const -> Font; + auto foregroundColor(bool recursive = false) const -> Color; + auto icon() const -> image; + auto setAlignment(Alignment alignment = {}) -> type&; + auto setBackgroundColor(Color color = {}) -> type&; + auto setCheckable(bool checkable = true) -> type&; + auto setChecked(bool checked = true) -> type&; + auto setForegroundColor(Color color = {}) -> type&; + auto setIcon(const image& icon = {}) -> type&; + auto setText(const string& text = "") -> type&; + auto text() const -> string; + +//private: + struct State { + Alignment alignment; + Color backgroundColor; + bool checkable = false; + bool checked = false; + Color foregroundColor; + image icon; + string text; + } state; +}; +#endif diff --git a/hiro/core/widget/table-view-column.cpp b/hiro/core/widget/table-view-column.cpp index 06af8b84..6e8c0282 100644 --- a/hiro/core/widget/table-view-column.cpp +++ b/hiro/core/widget/table-view-column.cpp @@ -42,7 +42,7 @@ auto mTableViewColumn::icon() const -> image { } auto mTableViewColumn::remove() -> type& { - if(auto tableViewHeader = parentTableViewHeader()) tableViewHeader->remove(*this); + if(auto tableView = parentTableView()) tableView->remove(*this); return *this; } @@ -105,9 +105,15 @@ auto mTableViewColumn::setResizable(bool resizable) -> type& { return *this; } -auto mTableViewColumn::setSortable(bool sortable) -> type& { - state.sortable = sortable; - signal(setSortable, sortable); +auto mTableViewColumn::setSorting(Sort sorting) -> type& { + if(auto tableView = parentTableView()) { + for(auto& column : tableView->state.columns) { + column->state.sorting = Sort::None; + signalex(column, setSorting, Sort::None); + } + } + state.sorting = sorting; + signal(setSorting, sorting); return *this; } @@ -136,8 +142,8 @@ auto mTableViewColumn::setWidth(float width) -> type& { return *this; } -auto mTableViewColumn::sortable() const -> bool { - return state.sortable; +auto mTableViewColumn::sorting() const -> Sort { + return state.sorting; } auto mTableViewColumn::text() const -> string { diff --git a/hiro/core/widget/table-view-column.hpp b/hiro/core/widget/table-view-column.hpp new file mode 100644 index 00000000..2c1ddf82 --- /dev/null +++ b/hiro/core/widget/table-view-column.hpp @@ -0,0 +1,53 @@ +#if defined(Hiro_TableView) +struct mTableViewColumn : mObject { + Declare(TableViewColumn) + + auto active() const -> bool; + auto alignment() const -> Alignment; + auto backgroundColor() const -> Color; + auto editable() const -> bool; + auto expandable() const -> bool; + auto foregroundColor() const -> Color; + auto horizontalAlignment() const -> float; + auto icon() const -> image; + auto remove() -> type& override; + auto resizable() const -> bool; + auto setActive() -> type&; + auto setAlignment(Alignment alignment = {}) -> type&; + auto setBackgroundColor(Color color = {}) -> type&; + auto setEditable(bool editable = true) -> type&; + auto setExpandable(bool expandable = true) -> type&; + auto setForegroundColor(Color color = {}) -> type&; + auto setHorizontalAlignment(float alignment = 0.0) -> type&; + auto setIcon(const image& icon = {}) -> type&; + auto setResizable(bool resizable = true) -> type&; + auto setSorting(Sort sorting = Sort::None) -> type&; + auto setText(const string& text = "") -> type&; + auto setVerticalAlignment(float alignment = 0.5) -> type&; + auto setVisible(bool visible = true) -> type&; + auto setWidth(float width = 0) -> type&; + auto sort(Sort sorting) -> type&; + auto sorting() const -> Sort; + auto text() const -> string; + auto verticalAlignment() const -> float; + auto width() const -> float; + +//private: + struct State { + bool active = false; + Alignment alignment; + Color backgroundColor; + bool editable = false; + bool expandable = false; + Color foregroundColor; + float horizontalAlignment = 0.0; + image icon; + bool resizable = true; + Sort sorting = Sort::None; + string text; + float verticalAlignment = 0.5; + bool visible = true; + float width = 0; + } state; +}; +#endif diff --git a/hiro/core/widget/table-view-header.cpp b/hiro/core/widget/table-view-header.cpp deleted file mode 100644 index bb352314..00000000 --- a/hiro/core/widget/table-view-header.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#if defined(Hiro_TableView) - -auto mTableViewHeader::allocate() -> pObject* { - return new pTableViewHeader(*this); -} - -auto mTableViewHeader::destruct() -> void { - for(auto& column : state.columns) column->destruct(); - mObject::destruct(); -} - -// - -auto mTableViewHeader::append(sTableViewColumn column) -> type& { - state.columns.append(column); - column->setParent(this, columnCount() - 1); - signal(append, column); - return *this; -} - -auto mTableViewHeader::column(unsigned position) const -> TableViewColumn { - if(position < columnCount()) return state.columns[position]; - return {}; -} - -auto mTableViewHeader::columnCount() const -> unsigned { - return state.columns.size(); -} - -auto mTableViewHeader::columns() const -> vector { - vector columns; - for(auto& column : state.columns) columns.append(column); - return columns; -} - -auto mTableViewHeader::remove() -> type& { - if(auto tableView = parentTableView()) tableView->remove(*this); - return *this; -} - -auto mTableViewHeader::remove(sTableViewColumn column) -> type& { - signal(remove, column); - state.columns.remove(column->offset()); - for(auto n : range(column->offset(), columnCount())) { - state.columns[n]->adjustOffset(-1); - } - column->setParent(); - return *this; -} - -auto mTableViewHeader::reset() -> type& { - while(state.columns) remove(state.columns.right()); - return *this; -} - -auto mTableViewHeader::setParent(mObject* parent, signed offset) -> type& { - for(auto& column : state.columns) column->destruct(); - mObject::setParent(parent, offset); - for(auto& column : state.columns) column->setParent(this, column->offset()); - return *this; -} - -#endif diff --git a/hiro/core/widget/table-view-item.hpp b/hiro/core/widget/table-view-item.hpp new file mode 100644 index 00000000..aac43fc5 --- /dev/null +++ b/hiro/core/widget/table-view-item.hpp @@ -0,0 +1,34 @@ +#if defined(Hiro_TableView) +struct mTableViewItem : mObject { + Declare(TableViewItem) + + auto alignment() const -> Alignment; + auto append(sTableViewCell cell) -> type&; + auto backgroundColor() const -> Color; + auto cell(uint position) const -> TableViewCell; + auto cellCount() const -> uint; + auto cells() const -> vector; + auto foregroundColor() const -> Color; + auto remove() -> type& override; + auto remove(sTableViewCell cell) -> type&; + auto reset() -> type&; + auto selected() const -> bool; + auto setAlignment(Alignment alignment = {}) -> type&; + auto setBackgroundColor(Color color = {}) -> type&; + auto setFocused() -> type& override; + auto setForegroundColor(Color color = {}) -> type&; + auto setParent(mObject* parent = nullptr, int offset = -1) -> type& override; + auto setSelected(bool selected = true) -> type&; + +//private: + struct State { + Alignment alignment; + Color backgroundColor; + vector cells; + Color foregroundColor; + bool selected = false; + } state; + + auto destruct() -> void override; +}; +#endif diff --git a/hiro/core/widget/table-view.cpp b/hiro/core/widget/table-view.cpp index 6b40953e..f07cb503 100644 --- a/hiro/core/widget/table-view.cpp +++ b/hiro/core/widget/table-view.cpp @@ -6,7 +6,7 @@ auto mTableView::allocate() -> pObject* { auto mTableView::destruct() -> void { for(auto& item : state.items) item->destruct(); - if(auto& header = state.header) header->destruct(); + for(auto& column : state.columns) column->destruct(); mWidget::destruct(); } @@ -16,11 +16,10 @@ auto mTableView::alignment() const -> Alignment { return state.alignment; } -auto mTableView::append(sTableViewHeader header) -> type& { - if(auto& header = state.header) remove(header); - state.header = header; - header->setParent(this, 0); - signal(append, header); +auto mTableView::append(sTableViewColumn column) -> type& { + state.columns.append(column); + column->setParent(this, columnCount() - 1); + signal(append, column); return *this; } @@ -51,6 +50,21 @@ auto mTableView::bordered() const -> bool { return state.bordered; } +auto mTableView::column(uint position) const -> TableViewColumn { + if(position < columnCount()) return state.columns[position]; + return {}; +} + +auto mTableView::columnCount() const -> uint { + return state.columns.size(); +} + +auto mTableView::columns() const -> vector { + vector columns; + for(auto& column : columns) columns.append(column); + return columns; +} + auto mTableView::doActivate() const -> void { if(state.onActivate) return state.onActivate(); } @@ -79,8 +93,8 @@ auto mTableView::foregroundColor() const -> Color { return state.foregroundColor; } -auto mTableView::header() const -> TableViewHeader { - return state.header; +auto mTableView::headered() const -> bool { + return state.headered; } auto mTableView::item(unsigned position) const -> TableViewItem { @@ -128,17 +142,20 @@ auto mTableView::onToggle(const function& callback) -> typ return *this; } -auto mTableView::remove(sTableViewHeader header) -> type& { - signal(remove, header); - header->setParent(); - state.header.reset(); +auto mTableView::remove(sTableViewColumn column) -> type& { + signal(remove, column); + state.columns.remove(column->offset()); + for(uint n : range(column->offset(), columnCount())) { + state.columns[n]->adjustOffset(-1); + } + column->setParent(); return *this; } auto mTableView::remove(sTableViewItem item) -> type& { signal(remove, item); state.items.remove(item->offset()); - for(auto n : range(item->offset(), itemCount())) { + for(uint n : range(item->offset(), itemCount())) { state.items[n]->adjustOffset(-1); } item->setParent(); @@ -146,8 +163,8 @@ auto mTableView::remove(sTableViewItem item) -> type& { } auto mTableView::reset() -> type& { - while(state.items) remove(state.items.right()); - if(auto& header = state.header) remove(header); + while(state.items) remove(state.items.last()); + while(state.columns) remove(state.columns.last()); return *this; } @@ -193,13 +210,51 @@ auto mTableView::setForegroundColor(Color color) -> type& { return *this; } +auto mTableView::setHeadered(bool headered) -> type& { + state.headered = headered; + signal(setHeadered, headered); + return *this; +} + auto mTableView::setParent(mObject* parent, signed offset) -> type& { for(auto& item : reverse(state.items)) item->destruct(); - if(auto& header = state.header) header->destruct(); + for(auto& column : reverse(state.columns)) column->destruct(); mObject::setParent(parent, offset); - if(auto& header = state.header) header->setParent(this, 0); + for(auto& column : state.columns) column->setParent(this, column->offset()); for(auto& item : state.items) item->setParent(this, item->offset()); return *this; } +auto mTableView::setSortable(bool sortable) -> type& { + state.sortable = sortable; + signal(setSortable, sortable); + return *this; +} + +auto mTableView::sort() -> type& { + Sort sorting = Sort::None; + uint offset = 0; + for(auto& column : state.columns) { + if(column->sorting() == Sort::None) continue; + sorting = column->sorting(); + offset = column->offset(); + break; + } + auto sorted = state.items; + sorted.sort([&](auto& lhs, auto& rhs) { + string x = offset < lhs->cellCount() ? lhs->state.cells[offset]->state.text : ""; + string y = offset < rhs->cellCount() ? rhs->state.cells[offset]->state.text : ""; + if(sorting == Sort::Ascending ) return string::icompare(x, y) < 0; + if(sorting == Sort::Descending) return string::icompare(y, x) < 0; + return false; //unreachable + }); + while(state.items) remove(state.items.last()); + for(auto& item : sorted) append(item); + return *this; +} + +auto mTableView::sortable() const -> bool { + return state.sortable; +} + #endif diff --git a/hiro/core/widget/table-view.hpp b/hiro/core/widget/table-view.hpp new file mode 100644 index 00000000..4f68eb37 --- /dev/null +++ b/hiro/core/widget/table-view.hpp @@ -0,0 +1,71 @@ +#if defined(Hiro_TableView) +struct mTableView : mWidget { + Declare(TableView) + using mObject::remove; + + auto alignment() const -> Alignment; + auto append(sTableViewColumn column) -> type&; + auto append(sTableViewItem item) -> type&; + auto backgroundColor() const -> Color; + auto batchable() const -> bool; + auto batched() const -> vector; + auto bordered() const -> bool; + auto column(uint position) const -> TableViewColumn; + auto columnCount() const -> uint; + auto columns() const -> vector; + auto doActivate() const -> void; + auto doChange() const -> void; + auto doContext() const -> void; + auto doEdit(sTableViewCell cell) const -> void; + auto doSort(sTableViewColumn column) const -> void; + auto doToggle(sTableViewCell cell) const -> void; + auto foregroundColor() const -> Color; + auto headered() const -> bool; + auto item(uint position) const -> TableViewItem; + auto itemCount() const -> uint; + auto items() const -> vector; + auto onActivate(const function& callback = {}) -> type&; + auto onChange(const function& callback = {}) -> type&; + auto onContext(const function& callback = {}) -> type&; + auto onEdit(const function& callback = {}) -> type&; + auto onSort(const function& callback = {}) -> type&; + auto onToggle(const function& callback = {}) -> type&; + auto remove(sTableViewColumn column) -> type&; + auto remove(sTableViewItem item) -> type&; + auto reset() -> type&; + auto resizeColumns() -> type&; + auto selected() const -> TableViewItem; + auto setAlignment(Alignment alignment = {}) -> type&; + auto setBackgroundColor(Color color = {}) -> type&; + auto setBatchable(bool batchable = true) -> type&; + auto setBordered(bool bordered = true) -> type&; + auto setForegroundColor(Color color = {}) -> type&; + auto setHeadered(bool headered = true) -> type&; + auto setParent(mObject* parent = nullptr, int offset = -1) -> type& override; + auto setSortable(bool sortable = true) -> type&; + auto sort() -> type&; + auto sortable() const -> bool; + +//private: + struct State { + uint activeColumn = 0; + Alignment alignment; + Color backgroundColor; + bool batchable = false; + bool bordered = false; + vector columns; + Color foregroundColor; + bool headered = false; + vector items; + function onActivate; + function onChange; + function onContext; + function onEdit; + function onSort; + function onToggle; + bool sortable = false; + } state; + + auto destruct() -> void override; +}; +#endif diff --git a/hiro/core/widget/widget.hpp b/hiro/core/widget/widget.hpp new file mode 100644 index 00000000..fa1e46c4 --- /dev/null +++ b/hiro/core/widget/widget.hpp @@ -0,0 +1,15 @@ +#if defined(Hiro_Widget) +struct mWidget : mSizable { + Declare(Widget) + + auto doSize() const -> void; + auto onSize(const function& callback = {}) -> type&; + auto remove() -> type& override; + +//private: +//sizeof(mWidget) == 8 + struct State { + function onSize; + } state; +}; +#endif diff --git a/hiro/extension/list-view.cpp b/hiro/extension/list-view.cpp index 89e27d4c..24802613 100644 --- a/hiro/extension/list-view.cpp +++ b/hiro/extension/list-view.cpp @@ -14,7 +14,7 @@ mListView::mListView() { } } }); - append(TableViewHeader().setVisible(false).append(TableViewColumn().setExpandable())); + append(TableViewColumn().setExpandable()); } auto mListView::batched() const -> vector { @@ -64,7 +64,7 @@ auto mListView::onToggle(const function& callback) -> type& auto mListView::reset() -> type& { mTableView::reset(); - append(TableViewHeader().setVisible(false).append(TableViewColumn().setExpandable())); + append(TableViewColumn().setExpandable()); return *this; } diff --git a/hiro/gtk/platform.cpp b/hiro/gtk/platform.cpp index 328cdf64..d85ae21d 100644 --- a/hiro/gtk/platform.cpp +++ b/hiro/gtk/platform.cpp @@ -52,7 +52,6 @@ #include "widget/tab-frame.cpp" #include "widget/tab-frame-item.cpp" #include "widget/table-view.cpp" -#include "widget/table-view-header.cpp" #include "widget/table-view-column.cpp" #include "widget/table-view-item.cpp" #include "widget/table-view-cell.cpp" diff --git a/hiro/gtk/platform.hpp b/hiro/gtk/platform.hpp index a06a117b..a4b5f6b3 100644 --- a/hiro/gtk/platform.hpp +++ b/hiro/gtk/platform.hpp @@ -56,7 +56,6 @@ #include "widget/tab-frame.hpp" #include "widget/tab-frame-item.hpp" #include "widget/table-view.hpp" -#include "widget/table-view-header.hpp" #include "widget/table-view-column.hpp" #include "widget/table-view-item.hpp" #include "widget/table-view-cell.hpp" diff --git a/hiro/gtk/widget/source-edit.cpp b/hiro/gtk/widget/source-edit.cpp index b1a6fd0d..5721ac8a 100644 --- a/hiro/gtk/widget/source-edit.cpp +++ b/hiro/gtk/widget/source-edit.cpp @@ -54,7 +54,9 @@ auto pSourceEdit::construct() -> void { gtk_container_add(gtkContainer, gtkWidgetSourceView); gtk_widget_show(gtkWidgetSourceView); + setEditable(state().editable); setText(state().text); + setWordWrap(state().wordWrap); g_signal_connect(G_OBJECT(gtkSourceBuffer), "changed", G_CALLBACK(SourceEdit_change), (gpointer)this); g_signal_connect(G_OBJECT(gtkSourceBuffer), "notify::cursor-position", G_CALLBACK(SourceEdit_move), (gpointer)this); @@ -82,6 +84,10 @@ auto pSourceEdit::setCursor(Cursor cursor) -> void { unlock(); } +auto pSourceEdit::setEditable(bool editable) -> void { + gtk_text_view_set_editable(gtkTextView, editable); +} + auto pSourceEdit::setFocused() -> void { gtk_widget_grab_focus(gtkWidgetSourceView); } @@ -128,6 +134,11 @@ auto pSourceEdit::setText(const string& text) -> void { unlock(); } +auto pSourceEdit::setWordWrap(bool wordWrap) -> void { + gtk_text_view_set_wrap_mode(gtkTextView, wordWrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE); + gtk_scrolled_window_set_policy(gtkScrolledWindow, wordWrap ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS, GTK_POLICY_ALWAYS); +} + auto pSourceEdit::text() const -> string { GtkTextIter startIter; gtk_text_buffer_get_start_iter(gtkTextBuffer, &startIter); diff --git a/hiro/gtk/widget/source-edit.hpp b/hiro/gtk/widget/source-edit.hpp index f8923187..178b40ba 100644 --- a/hiro/gtk/widget/source-edit.hpp +++ b/hiro/gtk/widget/source-edit.hpp @@ -6,8 +6,10 @@ struct pSourceEdit : pWidget { Declare(SourceEdit, Widget) auto setCursor(Cursor cursor) -> void; + auto setEditable(bool editable) -> void; auto setFocused() -> void override; auto setText(const string& text) -> void; + auto setWordWrap(bool wordWrap) -> void; auto text() const -> string; GtkScrolledWindow* gtkScrolledWindow = nullptr; diff --git a/hiro/gtk/widget/table-view-cell.cpp b/hiro/gtk/widget/table-view-cell.cpp index ad41e842..76126e45 100644 --- a/hiro/gtk/widget/table-view-cell.cpp +++ b/hiro/gtk/widget/table-view-cell.cpp @@ -48,13 +48,11 @@ auto pTableViewCell::_parent() -> maybe { auto pTableViewCell::_setState() -> void { if(auto parent = _parent()) { if(auto grandparent = _grandparent()) { - if(auto& tableViewHeader = grandparent->state().header) { - if(self().offset() < tableViewHeader->columnCount()) { - auto lock = grandparent->acquire(); - gtk_list_store_set(grandparent->gtkListStore, &parent->gtkIter, 3 * self().offset() + 0, state().checked, -1); - gtk_list_store_set(grandparent->gtkListStore, &parent->gtkIter, 3 * self().offset() + 1, CreatePixbuf(state().icon), -1); - gtk_list_store_set(grandparent->gtkListStore, &parent->gtkIter, 3 * self().offset() + 2, state().text.data(), -1); - } + if(self().offset() < grandparent->self().columnCount()) { + auto lock = grandparent->acquire(); + gtk_list_store_set(grandparent->gtkListStore, &parent->gtkIter, 3 * self().offset() + 0, state().checked, -1); + gtk_list_store_set(grandparent->gtkListStore, &parent->gtkIter, 3 * self().offset() + 1, CreatePixbuf(state().icon), -1); + gtk_list_store_set(grandparent->gtkListStore, &parent->gtkIter, 3 * self().offset() + 2, state().text.data(), -1); } } } diff --git a/hiro/gtk/widget/table-view-column.cpp b/hiro/gtk/widget/table-view-column.cpp index 05598aed..9e29d92d 100644 --- a/hiro/gtk/widget/table-view-column.cpp +++ b/hiro/gtk/widget/table-view-column.cpp @@ -3,8 +3,8 @@ namespace hiro { auto pTableViewColumn::construct() -> void { - if(auto grandparent = _grandparent()) { - auto handle = grandparent.data(); + if(auto parent = _parent()) { + auto handle = parent.data(); uint offset = self().offset(); #if HIRO_GTK==2 @@ -16,9 +16,12 @@ auto pTableViewColumn::construct() -> void { gtkHeaderIcon = gtk_image_new(); gtk_box_pack_start(GTK_BOX(gtkHeader), gtkHeaderIcon, false, false, 0); - gtkHeaderText = gtk_label_new(state().text); + gtkHeaderText = gtk_label_new(""); gtk_box_pack_start(GTK_BOX(gtkHeader), gtkHeaderText, true, false, 2); + gtkHeaderSort = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(gtkHeader), gtkHeaderSort, false, false, 0); + gtkColumn = gtk_tree_view_column_new(); gtk_tree_view_column_set_sizing(gtkColumn, GTK_TREE_VIEW_COLUMN_FIXED); gtk_tree_view_column_set_title(gtkColumn, ""); @@ -43,24 +46,34 @@ auto pTableViewColumn::construct() -> void { g_signal_connect(G_OBJECT(gtkCellText), "edited", G_CALLBACK(TableView_edit), (gpointer)handle); g_signal_connect(G_OBJECT(gtkCellToggle), "toggled", G_CALLBACK(TableView_toggle), (gpointer)handle); - gtk_tree_view_append_column(grandparent->gtkTreeView, gtkColumn); + gtk_tree_view_append_column(parent->gtkTreeView, gtkColumn); gtk_widget_show_all(gtkHeader); - grandparent->_createModel(); + parent->_createModel(); - _setState(); + gtk_tree_view_column_set_clickable(gtkColumn, parent->state().sortable); + + if(state().active) setActive(); + setEditable(state().editable); + setIcon(state().icon); + setResizable(state().resizable); + setSorting(state().sorting); + setText(state().text); + setVisible(state().visible); } } auto pTableViewColumn::destruct() -> void { - if(auto grandparent = _grandparent()) { - gtk_tree_view_remove_column(grandparent->gtkTreeView, gtkColumn); + if(auto parent = _parent()) { + gtk_tree_view_remove_column(parent->gtkTreeView, gtkColumn); gtkColumn = nullptr; - grandparent->_createModel(); + parent->_createModel(); } } auto pTableViewColumn::setActive() -> void { - _setState(); + if(auto parent = _parent()) { + gtk_tree_view_set_search_column(parent->gtkTreeView, 3 * self().offset() + 2); + } } auto pTableViewColumn::setAlignment(Alignment alignment) -> void { @@ -74,8 +87,8 @@ auto pTableViewColumn::setEditable(bool editable) -> void { } auto pTableViewColumn::setExpandable(bool expandable) -> void { - if(auto grandparent = _grandparent()) { - grandparent->resizeColumns(); + if(auto parent = _parent()) { + parent->resizeColumns(); } } @@ -94,47 +107,35 @@ auto pTableViewColumn::setIcon(const image& icon) -> void { } auto pTableViewColumn::setResizable(bool resizable) -> void { - _setState(); + gtk_tree_view_column_set_resizable(gtkColumn, resizable); } -auto pTableViewColumn::setSortable(bool sortable) -> void { - _setState(); +auto pTableViewColumn::setSorting(Sort sorting) -> void { + string text; + if(sorting == Sort::Ascending ) text = "\u25b4"; + if(sorting == Sort::Descending) text = "\u25be"; + gtk_label_set_text(GTK_LABEL(gtkHeaderSort), text); } auto pTableViewColumn::setText(const string& text) -> void { - _setState(); + gtk_label_set_text(GTK_LABEL(gtkHeaderText), text); } auto pTableViewColumn::setVisible(bool visible) -> void { - _setState(); + gtk_tree_view_column_set_visible(gtkColumn, visible); } auto pTableViewColumn::setWidth(signed width) -> void { - if(auto grandparent = _grandparent()) { - grandparent->resizeColumns(); + if(auto parent = _parent()) { + parent->resizeColumns(); } } -auto pTableViewColumn::_grandparent() -> maybe { - if(auto parent = _parent()) return parent->_parent(); - return nothing; -} - -auto pTableViewColumn::_parent() -> maybe { - if(auto parent = self().parentTableViewHeader()) { +auto pTableViewColumn::_parent() -> maybe { + if(auto parent = self().parentTableView()) { if(auto self = parent->self()) return *self; } - return nothing; -} - -auto pTableViewColumn::_setState() -> void { - if(auto grandparent = _grandparent()) { - gtk_tree_view_set_search_column(grandparent->gtkTreeView, 3 * self().offset() + 2); - gtk_tree_view_column_set_resizable(gtkColumn, state().resizable); - gtk_tree_view_column_set_clickable(gtkColumn, state().sortable); - gtk_label_set_text(GTK_LABEL(gtkHeaderText), state().text); - gtk_tree_view_column_set_visible(gtkColumn, self().visible()); - } + return {}; } } diff --git a/hiro/gtk/widget/table-view-column.hpp b/hiro/gtk/widget/table-view-column.hpp index 882f7a19..7daba503 100644 --- a/hiro/gtk/widget/table-view-column.hpp +++ b/hiro/gtk/widget/table-view-column.hpp @@ -15,20 +15,19 @@ struct pTableViewColumn : pObject { auto setHorizontalAlignment(double) -> void {} auto setIcon(const image& icon) -> void; auto setResizable(bool resizable) -> void; - auto setSortable(bool sortable) -> void; + auto setSorting(Sort sorting) -> void; auto setText(const string& text) -> void; auto setVerticalAlignment(double) -> void {} auto setVisible(bool visible) -> void override; auto setWidth(signed width) -> void; - auto _grandparent() -> maybe; - auto _parent() -> maybe; - auto _setState() -> void; + auto _parent() -> maybe; GtkTreeViewColumn* gtkColumn = nullptr; GtkWidget* gtkHeader = nullptr; GtkWidget* gtkHeaderIcon = nullptr; GtkWidget* gtkHeaderText = nullptr; + GtkWidget* gtkHeaderSort = nullptr; GtkCellRenderer* gtkCellToggle = nullptr; GtkCellRenderer* gtkCellIcon = nullptr; GtkCellRenderer* gtkCellText = nullptr; diff --git a/hiro/gtk/widget/table-view-header.cpp b/hiro/gtk/widget/table-view-header.cpp deleted file mode 100644 index f8488fec..00000000 --- a/hiro/gtk/widget/table-view-header.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#if defined(Hiro_TableView) - -namespace hiro { - -auto pTableViewHeader::construct() -> void { - _setState(); -} - -auto pTableViewHeader::destruct() -> void { -} - -auto pTableViewHeader::append(sTableViewColumn column) -> void { - _setState(); -} - -auto pTableViewHeader::remove(sTableViewColumn column) -> void { -} - -auto pTableViewHeader::setVisible(bool visible) -> void { - _setState(); -} - -auto pTableViewHeader::_parent() -> maybe { - if(auto parent = self().parentTableView()) { - if(auto self = parent->self()) return *self; - } - return nothing; -} - -auto pTableViewHeader::_setState() -> void { - if(auto parent = _parent()) { - gtk_tree_view_set_headers_visible(parent->gtkTreeView, self().visible()); - for(auto& column : state().columns) { - if(auto self = column->self()) self->_setState(); - } - } -} - -} - -#endif diff --git a/hiro/gtk/widget/table-view-header.hpp b/hiro/gtk/widget/table-view-header.hpp deleted file mode 100644 index 814f3ac8..00000000 --- a/hiro/gtk/widget/table-view-header.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#if defined(Hiro_TableView) - -namespace hiro { - -struct pTableViewHeader : pObject { - Declare(TableViewHeader, Object) - - auto append(sTableViewColumn column) -> void; - auto remove(sTableViewColumn column) -> void; - auto setVisible(bool visible) -> void override; - - auto _parent() -> maybe; - auto _setState() -> void; -}; - -} - -#endif diff --git a/hiro/gtk/widget/table-view.cpp b/hiro/gtk/widget/table-view.cpp index e0e957ff..d01b50a8 100644 --- a/hiro/gtk/widget/table-view.cpp +++ b/hiro/gtk/widget/table-view.cpp @@ -34,6 +34,8 @@ auto pTableView::construct() -> void { setBordered(state().bordered); setFont(self().font(true)); setForegroundColor(state().foregroundColor); + setHeadered(state().headered); + setSortable(state().sortable); g_signal_connect(G_OBJECT(gtkTreeView), "button-press-event", G_CALLBACK(TableView_buttonEvent), (gpointer)this); g_signal_connect(G_OBJECT(gtkTreeView), "button-release-event", G_CALLBACK(TableView_buttonEvent), (gpointer)this); @@ -50,7 +52,7 @@ auto pTableView::destruct() -> void { gtk_widget_destroy(gtkWidget); } -auto pTableView::append(sTableViewHeader header) -> void { +auto pTableView::append(sTableViewColumn column) -> void { } auto pTableView::append(sTableViewItem item) -> void { @@ -60,48 +62,44 @@ auto pTableView::focused() const -> bool { return gtk_widget_has_focus(GTK_WIDGET(gtkTreeView)); } -auto pTableView::remove(sTableViewHeader header) -> void { +auto pTableView::remove(sTableViewColumn column) -> void { } auto pTableView::remove(sTableViewItem item) -> void { } auto pTableView::resizeColumns() -> void { - lock(); + auto lock = acquire(); - if(auto& header = state().header) { - vector widths; - signed minimumWidth = 0; - signed expandable = 0; - for(auto column : range(header->columnCount())) { - signed width = _width(column); - widths.append(width); - minimumWidth += width; - if(header->column(column).expandable()) expandable++; - } - - signed maximumWidth = self().geometry().width() - 6; - if(auto scrollBar = gtk_scrolled_window_get_vscrollbar(gtkScrolledWindow)) { - GtkAllocation allocation; - gtk_widget_get_allocation(scrollBar, &allocation); - if(gtk_widget_get_visible(scrollBar)) maximumWidth -= allocation.width; - } - - signed expandWidth = 0; - if(expandable && maximumWidth > minimumWidth) { - expandWidth = (maximumWidth - minimumWidth) / expandable; - } - - for(auto column : range(header->columnCount())) { - if(auto self = header->state.columns[column]->self()) { - signed width = widths[column]; - if(self->state().expandable) width += expandWidth; - gtk_tree_view_column_set_fixed_width(self->gtkColumn, width); - } - } + vector widths; + signed minimumWidth = 0; + signed expandable = 0; + for(uint position : range(self().columnCount())) { + signed width = _width(position); + widths.append(width); + minimumWidth += width; + if(self().column(position).expandable()) expandable++; } - unlock(); + signed maximumWidth = self().geometry().width() - 6; + if(auto scrollBar = gtk_scrolled_window_get_vscrollbar(gtkScrolledWindow)) { + GtkAllocation allocation; + gtk_widget_get_allocation(scrollBar, &allocation); + if(gtk_widget_get_visible(scrollBar)) maximumWidth -= allocation.width; + } + + signed expandWidth = 0; + if(expandable && maximumWidth > minimumWidth) { + expandWidth = (maximumWidth - minimumWidth) / expandable; + } + + for(uint position : range(self().columnCount())) { + if(auto column = self().column(position)->self()) { + signed width = widths[position]; + if(column->state().expandable) width += expandWidth; + gtk_tree_view_column_set_fixed_width(column->gtkColumn, width); + } + } } auto pTableView::setAlignment(Alignment alignment) -> void { @@ -131,9 +129,6 @@ auto pTableView::setFocused() -> void { } auto pTableView::setFont(const Font& font) -> void { - if(auto& header = state().header) { - if(auto self = header->self()) self->_setState(); - } } auto pTableView::setForegroundColor(Color color) -> void { @@ -143,10 +138,18 @@ auto pTableView::setForegroundColor(Color color) -> void { auto pTableView::setGeometry(Geometry geometry) -> void { pWidget::setGeometry(geometry); - if(auto& header = state().header) { - for(auto& column : header->state.columns) { - if(column->state.expandable) return resizeColumns(); - } + for(auto& column : state().columns) { + if(column->expandable()) return resizeColumns(); + } +} + +auto pTableView::setHeadered(bool headered) -> void { + gtk_tree_view_set_headers_visible(gtkTreeView, headered); +} + +auto pTableView::setSortable(bool sortable) -> void { + for(auto& column : state().columns) { + if(auto self = column->self()) gtk_tree_view_column_set_clickable(self->gtkColumn, sortable); } } @@ -170,14 +173,15 @@ auto pTableView::_cellWidth(unsigned _row, unsigned _column) -> unsigned { auto pTableView::_columnWidth(unsigned _column) -> unsigned { unsigned width = 8; - if(auto& header = state().header) { - if(auto column = header->column(_column)) { - if(auto& icon = column->state.icon) { - width += icon.width() + 2; - } - if(auto& text = column->state.text) { - width += pFont::size(column->font(true), text).width(); - } + if(auto column = self().column(_column)) { + if(auto& icon = column->state.icon) { + width += icon.width() + 2; + } + if(auto& text = column->state.text) { + width += pFont::size(column->font(true), text).width(); + } + if(column->state.sorting != Sort::None) { + width += 20; } } return width; @@ -191,14 +195,12 @@ auto pTableView::_createModel() -> void { gtkTreeModel = nullptr; vector types; - if(auto& header = state().header) { - for(auto column : header->state.columns) { - if(auto self = column->self()) { - if(!self->gtkColumn) continue; //may not have been created yet; or recently destroyed - types.append(G_TYPE_BOOLEAN); - types.append(GDK_TYPE_PIXBUF); - types.append(G_TYPE_STRING); - } + for(auto& column : state().columns) { + if(auto self = column->self()) { + if(!self->gtkColumn) continue; //may not have been created yet; or recently destroyed + types.append(G_TYPE_BOOLEAN); + types.append(GDK_TYPE_PIXBUF); + types.append(G_TYPE_STRING); } } if(!types) return; //no columns available @@ -230,45 +232,43 @@ auto pTableView::_doDataFunc(GtkTreeViewColumn* gtkColumn, GtkCellRenderer* rend auto row = toNatural(path); g_free(path); - if(auto& header = state().header) { - for(auto& column : header->state.columns) { - if(auto p = column->self()) { - if(renderer != GTK_CELL_RENDERER(p->gtkCellToggle) - && renderer != GTK_CELL_RENDERER(p->gtkCellIcon) - && renderer != GTK_CELL_RENDERER(p->gtkCellText) - ) continue; - if(auto item = self().item(row)) { - if(auto cell = item->cell(column->offset())) { - if(renderer == GTK_CELL_RENDERER(p->gtkCellToggle)) { - gtk_cell_renderer_set_visible(renderer, cell->state.checkable); - } else if(renderer == GTK_CELL_RENDERER(p->gtkCellText)) { - auto alignment = cell->alignment(true); - if(!alignment) alignment = {0.0, 0.5}; - //note: below line will center column header text; but causes strange glitches - //(specifically, windows fail to respond to the close button ... some kind of heap corruption inside GTK+) - //gtk_tree_view_column_set_alignment(gtkColumn, alignment.horizontal()); - gtk_cell_renderer_set_alignment(renderer, alignment.horizontal(), alignment.vertical()); - auto pangoAlignment = PANGO_ALIGN_CENTER; - if(alignment.horizontal() < 0.333) pangoAlignment = PANGO_ALIGN_LEFT; - if(alignment.horizontal() > 0.666) pangoAlignment = PANGO_ALIGN_RIGHT; - g_object_set(G_OBJECT(renderer), "alignment", pangoAlignment, nullptr); - auto font = pFont::create(cell->font(true)); - g_object_set(G_OBJECT(renderer), "font-desc", font, nullptr); - pango_font_description_free(font); - if(auto color = cell->foregroundColor(true)) { - auto gdkColor = CreateColor(color); - if(settings.theme.widgetColors) g_object_set(G_OBJECT(renderer), "foreground-gdk", &gdkColor, nullptr); - } else { - g_object_set(G_OBJECT(renderer), "foreground-set", false, nullptr); - } - } - if(auto color = cell->backgroundColor(true)) { + for(auto& column : state().columns) { + if(auto p = column->self()) { + if(renderer != GTK_CELL_RENDERER(p->gtkCellToggle) + && renderer != GTK_CELL_RENDERER(p->gtkCellIcon) + && renderer != GTK_CELL_RENDERER(p->gtkCellText) + ) continue; + if(auto item = self().item(row)) { + if(auto cell = item->cell(column->offset())) { + if(renderer == GTK_CELL_RENDERER(p->gtkCellToggle)) { + gtk_cell_renderer_set_visible(renderer, cell->state.checkable); + } else if(renderer == GTK_CELL_RENDERER(p->gtkCellText)) { + auto alignment = cell->alignment(true); + if(!alignment) alignment = {0.0, 0.5}; + //note: below line will center column header text; but causes strange glitches + //(specifically, windows fail to respond to the close button ... some kind of heap corruption inside GTK+) + //gtk_tree_view_column_set_alignment(gtkColumn, alignment.horizontal()); + gtk_cell_renderer_set_alignment(renderer, alignment.horizontal(), alignment.vertical()); + auto pangoAlignment = PANGO_ALIGN_CENTER; + if(alignment.horizontal() < 0.333) pangoAlignment = PANGO_ALIGN_LEFT; + if(alignment.horizontal() > 0.666) pangoAlignment = PANGO_ALIGN_RIGHT; + g_object_set(G_OBJECT(renderer), "alignment", pangoAlignment, nullptr); + auto font = pFont::create(cell->font(true)); + g_object_set(G_OBJECT(renderer), "font-desc", font, nullptr); + pango_font_description_free(font); + if(auto color = cell->foregroundColor(true)) { auto gdkColor = CreateColor(color); - if(settings.theme.widgetColors) g_object_set(G_OBJECT(renderer), "cell-background-gdk", &gdkColor, nullptr); + if(settings.theme.widgetColors) g_object_set(G_OBJECT(renderer), "foreground-gdk", &gdkColor, nullptr); } else { - g_object_set(G_OBJECT(renderer), "cell-background-set", false, nullptr); + g_object_set(G_OBJECT(renderer), "foreground-set", false, nullptr); } } + if(auto color = cell->backgroundColor(true)) { + auto gdkColor = CreateColor(color); + if(settings.theme.widgetColors) g_object_set(G_OBJECT(renderer), "cell-background-gdk", &gdkColor, nullptr); + } else { + g_object_set(G_OBJECT(renderer), "cell-background-set", false, nullptr); + } } } } @@ -276,19 +276,17 @@ auto pTableView::_doDataFunc(GtkTreeViewColumn* gtkColumn, GtkCellRenderer* rend } auto pTableView::_doEdit(GtkCellRendererText* gtkCellRendererText, const char* path, const char* text) -> void { - if(auto& header = state().header) { - for(auto& column : header->state.columns) { - if(auto delegate = column->self()) { - if(gtkCellRendererText == GTK_CELL_RENDERER_TEXT(delegate->gtkCellText)) { - auto row = toNatural(path); - if(auto item = self().item(row)) { - if(auto cell = item->cell(column->offset())) { - if(string{text} != cell->state.text) { - cell->setText(text); - if(!locked()) self().doEdit(cell); - } - return; + for(auto& column : state().columns) { + if(auto delegate = column->self()) { + if(gtkCellRendererText == GTK_CELL_RENDERER_TEXT(delegate->gtkCellText)) { + auto row = toNatural(path); + if(auto item = self().item(row)) { + if(auto cell = item->cell(column->offset())) { + if(string{text} != cell->state.text) { + cell->setText(text); + if(!locked()) self().doEdit(cell); } + return; } } } @@ -326,13 +324,11 @@ auto pTableView::_doEvent(GdkEventButton* event) -> signed { } auto pTableView::_doHeaderActivate(GtkTreeViewColumn* gtkTreeViewColumn) -> void { - if(auto& header = state().header) { - for(auto& column : header->state.columns) { - if(auto delegate = column->self()) { - if(gtkTreeViewColumn == delegate->gtkColumn) { - if(!locked()) self().doSort(column); - return; - } + for(auto& column : state().columns) { + if(auto delegate = column->self()) { + if(gtkTreeViewColumn == delegate->gtkColumn) { + if(!locked()) self().doSort(column); + return; } } } @@ -348,17 +344,15 @@ auto pTableView::_doMouseMove() -> signed { } auto pTableView::_doToggle(GtkCellRendererToggle* gtkCellRendererToggle, const char* path) -> void { - if(auto& header = state().header) { - for(auto& column : header->state.columns) { - if(auto delegate = column->self()) { - if(gtkCellRendererToggle == GTK_CELL_RENDERER_TOGGLE(delegate->gtkCellToggle)) { - auto row = toNatural(path); - if(auto item = self().item(row)) { - if(auto cell = item->cell(column->offset())) { - cell->setChecked(!cell->checked()); - if(!locked()) self().doToggle(cell); - return; - } + for(auto& column : state().columns) { + if(auto delegate = column->self()) { + if(gtkCellRendererToggle == GTK_CELL_RENDERER_TOGGLE(delegate->gtkCellToggle)) { + auto row = toNatural(path); + if(auto item = self().item(row)) { + if(auto cell = item->cell(column->offset())) { + cell->setChecked(!cell->checked()); + if(!locked()) self().doToggle(cell); + return; } } } @@ -412,17 +406,14 @@ auto pTableView::_updateSelected() -> void { } auto pTableView::_width(unsigned column) -> unsigned { - if(auto& header = state().header) { - if(auto width = header->column(column).width()) return width; - unsigned width = 1; - if(!header->column(column).visible()) return width; - if(header->visible()) width = max(width, _columnWidth(column)); - for(auto row : range(self().itemCount())) { - width = max(width, _cellWidth(row, column)); - } - return width; + if(auto width = self().column(column).width()) return width; + unsigned width = 1; + if(!self().column(column).visible()) return width; + if(self().headered()) width = max(width, _columnWidth(column)); + for(auto row : range(self().itemCount())) { + width = max(width, _cellWidth(row, column)); } - return 1; + return width; } } diff --git a/hiro/gtk/widget/table-view.hpp b/hiro/gtk/widget/table-view.hpp index 71a482e7..b34e7ef0 100644 --- a/hiro/gtk/widget/table-view.hpp +++ b/hiro/gtk/widget/table-view.hpp @@ -5,10 +5,10 @@ namespace hiro { struct pTableView : pWidget { Declare(TableView, Widget) - auto append(sTableViewHeader column) -> void; + auto append(sTableViewColumn column) -> void; auto append(sTableViewItem item) -> void; auto focused() const -> bool override; - auto remove(sTableViewHeader column) -> void; + auto remove(sTableViewColumn column) -> void; auto remove(sTableViewItem item) -> void; auto resizeColumns() -> void; auto setAlignment(Alignment alignment) -> void; @@ -19,21 +19,23 @@ struct pTableView : pWidget { auto setFont(const Font& font) -> void override; auto setForegroundColor(Color color) -> void; auto setGeometry(Geometry geometry) -> void override; + auto setHeadered(bool headered) -> void; + auto setSortable(bool sortable) -> void; - auto _cellWidth(unsigned row, unsigned column) -> unsigned; - auto _columnWidth(unsigned column) -> unsigned; + auto _cellWidth(uint row, uint column) -> uint; + auto _columnWidth(uint column) -> uint; auto _createModel() -> void; auto _doActivate() -> void; auto _doChange() -> void; auto _doContext() -> void; auto _doDataFunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeIter* iter) -> void; auto _doEdit(GtkCellRendererText* gtkCellRendererText, const char* path, const char* text) -> void; - auto _doEvent(GdkEventButton* event) -> signed; + auto _doEvent(GdkEventButton* event) -> int; auto _doHeaderActivate(GtkTreeViewColumn* column) -> void; - auto _doMouseMove() -> signed; + auto _doMouseMove() -> int; auto _doToggle(GtkCellRendererToggle* gtkCellRendererToggle, const char* path) -> void; auto _updateSelected() -> void; - auto _width(unsigned column) -> unsigned; + auto _width(uint column) -> uint; GtkScrolledWindow* gtkScrolledWindow = nullptr; GtkWidget* gtkWidgetChild = nullptr; @@ -41,7 +43,7 @@ struct pTableView : pWidget { GtkTreeSelection* gtkTreeSelection = nullptr; GtkListStore* gtkListStore = nullptr; GtkTreeModel* gtkTreeModel = nullptr; - vector currentSelection; + vector currentSelection; }; } diff --git a/nall/decode/rle.hpp b/nall/decode/rle.hpp new file mode 100644 index 00000000..78f0a23d --- /dev/null +++ b/nall/decode/rle.hpp @@ -0,0 +1,46 @@ +#pragma once + +namespace nall { namespace Decode { + +template inline auto RLE(const uint8_t* data, uint remaining = ~0, uint minimum = 0) -> vector { + if(!minimum) minimum = max(1, 4 / sizeof(T)); + vector result; + + auto load = [&]() -> uint8_t { + if(!remaining) return 0x00; + return --remaining, *data++; + }; + + uint base = 0; + uint size = 0; + for(uint byte : range(sizeof(uint))) size |= load() << byte * 8; + size /= sizeof(T); + result.resize(size); + + auto read = [&]() -> T { + T value = 0; + for(uint byte : range(sizeof(T))) value |= load() << byte * 8; + return value; + }; + + auto write = [&](T value) -> void { + if(base >= size) return; + result[base++] = value; + }; + + while(base < size) { + auto byte = *data++; + if(byte < 128) { + byte++; + while(byte--) write(read()); + } else { + auto value = read(); + byte = (byte & 127) + minimum; + while(byte--) write(value); + } + } + + return result; +} + +}} diff --git a/nall/encode/bmp.hpp b/nall/encode/bmp.hpp index 03062212..6a842229 100644 --- a/nall/encode/bmp.hpp +++ b/nall/encode/bmp.hpp @@ -3,7 +3,7 @@ namespace nall { namespace Encode { struct BMP { - static auto create(const string& filename, const uint32_t* data, uint pitch, uint width, uint height, bool alpha) -> bool { + static auto create(const string& filename, const void* data, uint pitch, uint width, uint height, bool alpha) -> bool { file fp{filename, file::mode::write}; if(!fp) return false; @@ -35,7 +35,7 @@ struct BMP { pitch >>= 2; for(auto y : range(height)) { - const uint32_t* p = data + y * pitch; + auto p = (const uint32_t*)data + y * pitch; for(auto x : range(width)) fp.writel(*p++, bytesPerPixel); if(paddingLength) fp.writel(0, paddingLength); } diff --git a/nall/encode/rle.hpp b/nall/encode/rle.hpp new file mode 100644 index 00000000..f7bd7c41 --- /dev/null +++ b/nall/encode/rle.hpp @@ -0,0 +1,50 @@ +#pragma once + +namespace nall { namespace Encode { + +template inline auto RLE(const void* data_, uint size, uint minimum = 0) -> vector { + if(!minimum) minimum = max(1, 4 / sizeof(T)); + vector result; + + auto data = (const T*)data_; + uint base = 0; + uint skip = 0; + + for(uint byte : range(sizeof(uint))) result.append(size * sizeof(T) >> byte * 8); + + auto read = [&](uint offset) -> T { + if(offset >= size) return {}; + return data[offset]; + }; + + auto write = [&](T value) -> void { + for(uint byte : range(sizeof(T))) result.append(value >> byte * 8); + }; + + auto flush = [&]() -> void { + result.append(skip - 1); + do { write(read(base++)); } while(--skip); + }; + + while(base + skip < size) { + uint same = 1; + for(uint offset = base + skip + 1; offset < size; offset++) { + if(read(offset) != read(base + skip)) break; + if(++same == 127 + minimum) break; + } + + if(same < minimum) { + if(++skip == 128) flush(); + } else { + if(skip) flush(); + result.append(128 | same - minimum); + write(read(base)); + base += same; + } + } + if(skip) flush(); + + return result; +} + +}} diff --git a/nall/function.hpp b/nall/function.hpp index 8fcd255a..3b59e05c 100644 --- a/nall/function.hpp +++ b/nall/function.hpp @@ -7,6 +7,8 @@ namespace nall { template struct function; template struct function R> { + using cast = auto (*)(P...) -> R; + //value = true if auto L::operator()(P...) -> R exists template struct is_compatible { template static auto exists(T*) -> const typename is_same().operator()(declval

()...))>::type; @@ -16,11 +18,11 @@ template struct function R> { function() {} function(const function& source) { operator=(source); } - function(void* function) { if(function) callback = new global((auto (*)(P...) -> R)function); } function(auto (*function)(P...) -> R) { callback = new global(function); } template function(auto (C::*function)(P...) -> R, C* object) { callback = new member(function, object); } template function(auto (C::*function)(P...) const -> R, C* object) { callback = new member((auto (C::*)(P...) -> R)function, object); } template::value>> function(const L& object) { callback = new lambda(object); } + explicit function(void* function) { if(function) callback = new global((auto (*)(P...) -> R)function); } ~function() { if(callback) delete callback; } explicit operator bool() const { return callback; } @@ -35,6 +37,12 @@ template struct function R> { return *this; } + auto operator=(void* source) -> function& { + if(callback) { delete callback; callback = nullptr; } + callback = new global((auto (*)(P...) -> R)source); + return *this; + } + private: struct container { virtual auto operator()(P... p) const -> R = 0; diff --git a/nall/image.hpp b/nall/image.hpp index 03e54845..25c39e5f 100644 --- a/nall/image.hpp +++ b/nall/image.hpp @@ -7,7 +7,156 @@ #include #include #include -#include + +namespace nall { + +struct image { + enum class blend : uint { + add, + sourceAlpha, //color = sourceColor * sourceAlpha + targetColor * (1 - sourceAlpha) + sourceColor, //color = sourceColor + targetAlpha, //color = targetColor * targetAlpha + sourceColor * (1 - targetAlpha) + targetColor, //color = targetColor + }; + + struct channel { + channel(uint64_t mask, uint depth, uint shift) : _mask(mask), _depth(depth), _shift(shift) { + } + + auto operator==(const channel& source) const -> bool { + return _mask == source._mask && _depth == source._depth && _shift == source._shift; + } + + auto operator!=(const channel& source) const -> bool { + return !operator==(source); + } + + alwaysinline auto mask() const { return _mask; } + alwaysinline auto depth() const { return _depth; } + alwaysinline auto shift() const { return _shift; } + + private: + uint64_t _mask; + uint _depth; + uint _shift; + }; + + //core.hpp + inline image(const image& source); + inline image(image&& source); + inline image(bool endian, uint depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + inline image(const string& filename); + inline image(const vector& buffer); + inline image(const uint8_t* data, uint size); + inline image(); + inline ~image(); + + inline auto operator=(const image& source) -> image&; + inline auto operator=(image&& source) -> image&; + + inline explicit operator bool() const; + inline auto operator==(const image& source) const -> bool; + inline auto operator!=(const image& source) const -> bool; + + inline auto read(const uint8_t* data) const -> uint64_t; + inline auto write(uint8_t* data, uint64_t value) const -> void; + + inline auto free() -> void; + inline auto load(const string& filename) -> bool; + inline auto allocate(uint width, uint height) -> void; + inline auto allocate(const void* data, uint pitch, uint width, uint height) -> void; + + //fill.hpp + inline auto fill(uint64_t color = 0) -> void; + inline auto gradient(uint64_t a, uint64_t b, uint64_t c, uint64_t d) -> void; + inline auto gradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY, function callback) -> void; + inline auto crossGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto diamondGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto horizontalGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto radialGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto sphericalGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto squareGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + inline auto verticalGradient(uint64_t a, uint64_t b, int radiusX, int radiusY, int centerX, int centerY) -> void; + + //scale.hpp + inline auto scale(uint width, uint height, bool linear = true) -> void; + + //blend.hpp + inline auto impose(blend mode, uint targetX, uint targetY, image source, uint x, uint y, uint width, uint height) -> void; + + //utility.hpp + inline auto crop(uint x, uint y, uint width, uint height) -> bool; + inline auto alphaBlend(uint64_t alphaColor) -> void; + inline auto alphaMultiply() -> void; + inline auto transform(const image& source = {}) -> void; + inline auto transform(bool endian, uint depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) -> void; + + //static.hpp + static inline auto bitDepth(uint64_t color) -> uint; + static inline auto bitShift(uint64_t color) -> uint; + static inline auto normalize(uint64_t color, uint sourceDepth, uint targetDepth) -> uint64_t; + + //access + alwaysinline auto data() { return _data; } + alwaysinline auto data() const { return _data; } + alwaysinline auto width() const { return _width; } + alwaysinline auto height() const { return _height; } + + alwaysinline auto endian() const { return _endian; } + alwaysinline auto depth() const { return _depth; } + alwaysinline auto stride() const { return (_depth + 7) >> 3; } + + alwaysinline auto pitch() const { return _width * stride(); } + alwaysinline auto size() const { return _height * pitch(); } + + alwaysinline auto alpha() const { return _alpha; } + alwaysinline auto red() const { return _red; } + alwaysinline auto green() const { return _green; } + alwaysinline auto blue() const { return _blue; } + +private: + //core.hpp + inline auto allocate(uint width, uint height, uint stride) -> uint8_t*; + + //scale.hpp + inline auto scaleLinearWidth(uint width) -> void; + inline auto scaleLinearHeight(uint height) -> void; + inline auto scaleLinear(uint width, uint height) -> void; + inline auto scaleNearest(uint width, uint height) -> void; + + //load.hpp + inline auto loadBMP(const string& filename) -> bool; + inline auto loadBMP(const uint8_t* data, uint size) -> bool; + inline auto loadPNG(const string& filename) -> bool; + inline auto loadPNG(const uint8_t* data, uint size) -> bool; + + //interpolation.hpp + alwaysinline auto isplit(uint64_t* component, uint64_t color) -> void; + alwaysinline auto imerge(const uint64_t* component) -> uint64_t; + alwaysinline auto interpolate1f(uint64_t a, uint64_t b, double x) -> uint64_t; + alwaysinline auto interpolate1f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t; + alwaysinline auto interpolate1i(int64_t a, int64_t b, uint32_t x) -> uint64_t; + alwaysinline auto interpolate1i(int64_t a, int64_t b, int64_t c, int64_t d, uint32_t x, uint32_t y) -> uint64_t; + inline auto interpolate4f(uint64_t a, uint64_t b, double x) -> uint64_t; + inline auto interpolate4f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t; + inline auto interpolate4i(uint64_t a, uint64_t b, uint32_t x) -> uint64_t; + inline auto interpolate4i(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint32_t x, uint32_t y) -> uint64_t; + + uint8_t* _data = nullptr; + uint _width = 0; + uint _height = 0; + + bool _endian = 0; //0 = lsb, 1 = msb + uint _depth = 32; + + channel _alpha{255u << 24, 8, 24}; + channel _red {255u << 16, 8, 16}; + channel _green{255u << 8, 8, 8}; + channel _blue {255u << 0, 8, 0}; +}; + +} + #include #include #include diff --git a/nall/image/base.hpp b/nall/image/base.hpp deleted file mode 100644 index 9e3ac2e8..00000000 --- a/nall/image/base.hpp +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once - -namespace nall { - -struct image { - enum class blend : unsigned { - add, - sourceAlpha, //color = sourceColor * sourceAlpha + targetColor * (1 - sourceAlpha) - sourceColor, //color = sourceColor - targetAlpha, //color = targetColor * targetAlpha + sourceColor * (1 - targetAlpha) - targetColor, //color = targetColor - }; - - struct channel { - channel(uint64_t mask, unsigned depth, unsigned shift) : _mask(mask), _depth(depth), _shift(shift) { - } - - auto operator==(const channel& source) const -> bool { - return _mask == source._mask && _depth == source._depth && _shift == source._shift; - } - - auto operator!=(const channel& source) const -> bool { - return !operator==(source); - } - - alwaysinline auto mask() const { return _mask; } - alwaysinline auto depth() const { return _depth; } - alwaysinline auto shift() const { return _shift; } - - private: - uint64_t _mask; - unsigned _depth; - unsigned _shift; - }; - - //core.hpp - inline image(const image& source); - inline image(image&& source); - inline image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); - inline image(const string& filename); - inline image(const vector& buffer); - inline image(const uint8_t* data, unsigned size); - inline image(); - inline ~image(); - - inline auto operator=(const image& source) -> image&; - inline auto operator=(image&& source) -> image&; - - inline explicit operator bool() const; - inline auto operator==(const image& source) const -> bool; - inline auto operator!=(const image& source) const -> bool; - - inline auto read(const uint8_t* data) const -> uint64_t; - inline auto write(uint8_t* data, uint64_t value) const -> void; - - inline auto free() -> void; - inline auto load(const string& filename) -> bool; - inline auto allocate(unsigned width, unsigned height) -> void; - - //fill.hpp - inline auto fill(uint64_t color = 0) -> void; - inline auto gradient(uint64_t a, uint64_t b, uint64_t c, uint64_t d) -> void; - inline auto gradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY, function callback) -> void; - inline auto crossGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void; - inline auto diamondGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void; - inline auto horizontalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void; - inline auto radialGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void; - inline auto sphericalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void; - inline auto squareGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void; - inline auto verticalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) -> void; - - //scale.hpp - inline auto scale(unsigned width, unsigned height, bool linear = true) -> void; - - //blend.hpp - inline auto impose(blend mode, unsigned targetX, unsigned targetY, image source, unsigned x, unsigned y, unsigned width, unsigned height) -> void; - - //utility.hpp - inline auto crop(unsigned x, unsigned y, unsigned width, unsigned height) -> bool; - inline auto alphaBlend(uint64_t alphaColor) -> void; - inline auto alphaMultiply() -> void; - inline auto transform(const image& source = {}) -> void; - inline auto transform(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) -> void; - - //static.hpp - static inline auto bitDepth(uint64_t color) -> unsigned; - static inline auto bitShift(uint64_t color) -> unsigned; - static inline auto normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth) -> uint64_t; - - //access - alwaysinline auto data() { return _data; } - alwaysinline auto data() const { return _data; } - alwaysinline auto width() const { return _width; } - alwaysinline auto height() const { return _height; } - - alwaysinline auto endian() const { return _endian; } - alwaysinline auto depth() const { return _depth; } - alwaysinline auto stride() const { return (_depth + 7) >> 3; } - - alwaysinline auto pitch() const { return _width * stride(); } - alwaysinline auto size() const { return _height * pitch(); } - - alwaysinline auto alpha() const { return _alpha; } - alwaysinline auto red() const { return _red; } - alwaysinline auto green() const { return _green; } - alwaysinline auto blue() const { return _blue; } - -private: - //core.hpp - inline auto allocate(unsigned width, unsigned height, unsigned stride) -> uint8_t*; - - //scale.hpp - inline auto scaleLinearWidth(unsigned width) -> void; - inline auto scaleLinearHeight(unsigned height) -> void; - inline auto scaleLinear(unsigned width, unsigned height) -> void; - inline auto scaleNearest(unsigned width, unsigned height) -> void; - - //load.hpp - inline auto loadBMP(const string& filename) -> bool; - inline auto loadBMP(const uint8_t* data, unsigned size) -> bool; - inline auto loadPNG(const string& filename) -> bool; - inline auto loadPNG(const uint8_t* data, unsigned size) -> bool; - - //interpolation.hpp - alwaysinline auto isplit(uint64_t* component, uint64_t color) -> void; - alwaysinline auto imerge(const uint64_t* component) -> uint64_t; - alwaysinline auto interpolate1f(uint64_t a, uint64_t b, double x) -> uint64_t; - alwaysinline auto interpolate1f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t; - alwaysinline auto interpolate1i(int64_t a, int64_t b, uint32_t x) -> uint64_t; - alwaysinline auto interpolate1i(int64_t a, int64_t b, int64_t c, int64_t d, uint32_t x, uint32_t y) -> uint64_t; - inline auto interpolate4f(uint64_t a, uint64_t b, double x) -> uint64_t; - inline auto interpolate4f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) -> uint64_t; - inline auto interpolate4i(uint64_t a, uint64_t b, uint32_t x) -> uint64_t; - inline auto interpolate4i(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint32_t x, uint32_t y) -> uint64_t; - - uint8_t* _data = nullptr; - unsigned _width = 0; - unsigned _height = 0; - - bool _endian = 0; //0 = lsb, 1 = msb - unsigned _depth = 32; - - channel _alpha{255u << 24, 8, 24}; - channel _red {255u << 16, 8, 16}; - channel _green{255u << 8, 8, 8}; - channel _blue {255u << 0, 8, 0}; -}; - -} diff --git a/nall/image/core.hpp b/nall/image/core.hpp index 8fd61d72..bd0b7423 100644 --- a/nall/image/core.hpp +++ b/nall/image/core.hpp @@ -160,4 +160,14 @@ auto image::allocate(unsigned width, unsigned height, unsigned stride) -> uint8_ return data; } +//assumes image and data are in the same format; pitch is adapted to image +auto image::allocate(const void* data, uint pitch, uint width, uint height) -> void { + allocate(width, height); + for(uint y : range(height)) { + auto input = (const uint8_t*)data + y * pitch; + auto output = (uint8_t*)_data + y * this->pitch(); + memory::copy(output, input, width * stride()); + } +} + } diff --git a/nall/string.hpp b/nall/string.hpp index 51ec1a75..5d9d9e0d 100644 --- a/nall/string.hpp +++ b/nall/string.hpp @@ -47,6 +47,9 @@ template<> struct view { inline auto data() const -> const char*; inline auto size() const -> uint; + inline auto begin() const { return &_data[0]; } + inline auto end() const { return &_data[size()]; } + protected: string* _string; const char* _data; @@ -188,6 +191,8 @@ public: inline auto length() const -> uint; //find.hpp + inline auto contains(view characters) const -> maybe; + template inline auto _find(int, view) const -> maybe; inline auto find(view source) const -> maybe; @@ -307,6 +312,8 @@ struct string_format : vector { } #include +#include + #include #include #include @@ -320,13 +327,16 @@ struct string_format : vector { #include #include #include + #include #include #include #include + #include #include #include #include + #include #include diff --git a/nall/string/cast.hpp b/nall/string/cast.hpp index 669dfca4..8b17cfaa 100644 --- a/nall/string/cast.hpp +++ b/nall/string/cast.hpp @@ -234,6 +234,20 @@ template<> struct stringify&> { const view& _view; }; +template<> struct stringify { + stringify(const string_pascal& source) : _text(source) {} + auto data() const -> const char* { return _text.data(); } + auto size() const -> uint { return _text.size(); } + const string_pascal& _text; +}; + +template<> struct stringify { + stringify(const string_pascal& source) : _text(source) {} + auto data() const -> const char* { return _text.data(); } + auto size() const -> uint { return _text.size(); } + const string_pascal& _text; +}; + //pointers template struct stringify { diff --git a/nall/string/find.hpp b/nall/string/find.hpp index f3b16785..4d665539 100644 --- a/nall/string/find.hpp +++ b/nall/string/find.hpp @@ -2,6 +2,15 @@ namespace nall { +auto string::contains(view characters) const -> maybe { + for(uint x : range(size())) { + for(char y : characters) { + if(operator[](x) == y) return x; + } + } + return nothing; +} + template auto string::_find(int offset, view source) const -> maybe { if(source.size() == 0) return nothing; diff --git a/nall/string/markup/bml.hpp b/nall/string/markup/bml.hpp index 55b9c914..595afabd 100644 --- a/nall/string/markup/bml.hpp +++ b/nall/string/markup/bml.hpp @@ -40,7 +40,7 @@ protected: p += length; } - auto parseData(const char*& p) -> void { + auto parseData(const char*& p, view spacing) -> void { if(*p == '=' && *(p + 1) == '\"') { uint length = 2; while(p[length] && p[length] != '\n' && p[length] != '\"') length++; @@ -56,13 +56,13 @@ protected: } else if(*p == ':') { uint length = 1; while(p[length] && p[length] != '\n') length++; - _value = {slice(p, 1, length - 1), "\n"}; + _value = {slice(p, 1, length - 1).trimLeft(spacing, 1L), "\n"}; p += length; } } //read all attributes for a node - auto parseAttributes(const char*& p) -> void { + auto parseAttributes(const char*& p, view spacing) -> void { while(*p && *p != '\n') { if(*p != ' ') throw "Invalid node name"; while(*p == ' ') p++; //skip excess spaces @@ -73,31 +73,31 @@ protected: while(valid(p[length])) length++; if(length == 0) throw "Invalid attribute name"; node->_name = slice(p, 0, length); - node->parseData(p += length); + node->parseData(p += length, spacing); node->_value.trimRight("\n", 1L); _children.append(node); } } //read a node and all of its child nodes - auto parseNode(const vector& text, uint& y) -> void { + auto parseNode(const vector& text, uint& y, view spacing) -> void { const char* p = text[y++]; _metadata = parseDepth(p); parseName(p); - parseData(p); - parseAttributes(p); + parseData(p, spacing); + parseAttributes(p, spacing); while(y < text.size()) { uint depth = readDepth(text[y]); if(depth <= _metadata) break; if(text[y][depth] == ':') { - _value.append(slice(text[y++], depth + 1), "\n"); + _value.append(slice(text[y++], depth + 1).trimLeft(spacing, 1L), "\n"); continue; } SharedNode node(new ManagedNode); - node->parseNode(text, y); + node->parseNode(text, y, spacing); _children.append(node); } @@ -105,7 +105,7 @@ protected: } //read top-level nodes - auto parse(string document) -> void { + auto parse(string document, view spacing) -> void { //in order to simplify the parsing logic; we do an initial pass to normalize the data //the below code will turn '\r\n' into '\n'; skip empty lines; and skip comment lines char* p = document.get(), *output = p; @@ -134,37 +134,37 @@ protected: uint y = 0; while(y < text.size()) { SharedNode node(new ManagedNode); - node->parseNode(text, y); + node->parseNode(text, y, spacing); if(node->_metadata > 0) throw "Root nodes cannot be indented"; _children.append(node); } } - friend auto unserialize(const string&) -> Markup::Node; + friend auto unserialize(const string&, view) -> Markup::Node; }; -inline auto unserialize(const string& markup) -> Markup::Node { +inline auto unserialize(const string& markup, view spacing = {}) -> Markup::Node { SharedNode node(new ManagedNode); try { - node->parse(markup); + node->parse(markup, spacing); } catch(const char* error) { node.reset(); } return (Markup::SharedNode&)node; } -inline auto serialize(const Markup::Node& node, uint depth = 0) -> string { +inline auto serialize(const Markup::Node& node, view spacing = {}, uint depth = 0) -> string { if(!node.name()) { string result; for(auto leaf : node) { - result.append(serialize(leaf, depth)); + result.append(serialize(leaf, spacing, depth)); } return result; } string padding; padding.resize(depth * 2); - for(auto& byte : padding) byte = ' '; + padding.fill(' '); vector lines; if(auto value = node.value()) lines = value.split("\n"); @@ -172,16 +172,16 @@ inline auto serialize(const Markup::Node& node, uint depth = 0) -> string { string result; result.append(padding); result.append(node.name()); - if(lines.size() == 1) result.append(":", lines[0]); + if(lines.size() == 1) result.append(":", spacing, lines[0]); result.append("\n"); if(lines.size() > 1) { padding.append(" "); for(auto& line : lines) { - result.append(padding, ":", line, "\n"); + result.append(padding, ":", spacing, line, "\n"); } } for(auto leaf : node) { - result.append(serialize(leaf, depth + 1)); + result.append(serialize(leaf, spacing, depth + 1)); } return result; } diff --git a/nall/string/pascal.hpp b/nall/string/pascal.hpp new file mode 100644 index 00000000..31ca5d63 --- /dev/null +++ b/nall/string/pascal.hpp @@ -0,0 +1,79 @@ +#pragma once + +namespace nall { + +struct string_pascal { + using type = string_pascal; + + string_pascal(const char* text = nullptr) { + if(text && *text) { + uint size = strlen(text); + _data = memory::allocate(sizeof(uint) + size + 1); + ((uint*)_data)[0] = size; + memory::copy(_data + sizeof(uint), text, size); + _data[sizeof(uint) + size] = 0; + } + } + + string_pascal(const string& text) { + if(text.size()) { + _data = memory::allocate(sizeof(uint) + text.size() + 1); + ((uint*)_data)[0] = text.size(); + memory::copy(_data + sizeof(uint), text.data(), text.size()); + _data[sizeof(uint) + text.size()] = 0; + } + } + + string_pascal(const string_pascal& source) { operator=(source); } + string_pascal(string_pascal&& source) { operator=(move(source)); } + + ~string_pascal() { + if(_data) memory::free(_data); + } + + explicit operator bool() const { return _data; } + operator const char*() const { return _data ? _data + sizeof(uint) : nullptr; } + operator string() const { return _data ? string{_data + sizeof(uint)} : ""; } + + auto operator=(const string_pascal& source) -> type& { + if(this == &source) return *this; + if(_data) { memory::free(_data); _data = nullptr; } + if(source._data) { + uint size = source.size(); + _data = memory::allocate(sizeof(uint) + size); + memory::copy(_data, source._data, sizeof(uint) + size); + } + return *this; + } + + auto operator=(string_pascal&& source) -> type& { + if(this == &source) return *this; + if(_data) memory::free(_data); + _data = source._data; + source._data = nullptr; + return *this; + } + + auto operator==(view source) const -> bool { + return size() == source.size() && memory::compare(data(), source.data(), size()) == 0; + } + + auto operator!=(view source) const -> bool { + return size() != source.size() || memory::compare(data(), source.data(), size()) != 0; + } + + auto data() const -> char* { + if(!_data) return nullptr; + return _data + sizeof(uint); + } + + auto size() const -> uint { + if(!_data) return 0; + return ((uint*)_data)[0]; + } + +protected: + char* _data = nullptr; +}; + +} diff --git a/nall/vector.hpp b/nall/vector.hpp index 11563e68..63033329 100644 --- a/nall/vector.hpp +++ b/nall/vector.hpp @@ -110,7 +110,10 @@ struct vector_base { //utility.hpp auto sort(const function& comparator = [](auto& lhs, auto& rhs) { return lhs < rhs; }) -> void; + auto find(const function& comparator) -> maybe; auto find(const T& value) const -> maybe; + auto foreach(const function& callback) -> void; + auto foreach(const function& callback) -> void; private: T* _pool = nullptr; //pointer to first initialized element in pool diff --git a/nall/vector/utility.hpp b/nall/vector/utility.hpp index 1cb2b6ef..78af637a 100644 --- a/nall/vector/utility.hpp +++ b/nall/vector/utility.hpp @@ -6,9 +6,22 @@ template auto vector::sort(const function auto vector::find(const function& comparator) -> maybe { + for(uint n : range(size())) if(comparator(_pool[n])) return n; + return nothing; +} + template auto vector::find(const T& value) const -> maybe { for(uint n : range(size())) if(_pool[n] == value) return n; return nothing; } +template auto vector::foreach(const function& callback) -> void { + for(uint n : range(size())) callback(_pool[n]); +} + +template auto vector::foreach(const function& callback) -> void { + for(uint n : range(size())) callback(n, _pool[n]); +} + }