From 41e127a07c0ecf5744d2c6bdccb844b75fd8d6e9 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Sat, 4 Aug 2018 21:44:00 +1000 Subject: [PATCH] Update to v106r54 release. byuu says: Changes to hiro will break all but the GTK target. Not that it matters much given that the only ruby drivers that function are all on BSD anyway. But if you are fortunate enough to be able to run this ... you'll find lots of polishing improvements to the bsnes GUI. I posted some screenshots on Twitter, if anyone were interested. --- higan/emulator/emulator.hpp | 2 +- higan/target-bsnes/bsnes.hpp | 3 + .../presentation/presentation.cpp | 10 +- higan/target-bsnes/program/paths.cpp | 12 +- higan/target-bsnes/program/platform.cpp | 16 +- higan/target-bsnes/program/program.hpp | 10 +- higan/target-bsnes/program/states.cpp | 118 ++++--- higan/target-bsnes/program/utility.cpp | 21 +- higan/target-bsnes/settings/drivers.cpp | 2 + higan/target-bsnes/settings/emulator.cpp | 1 + higan/target-bsnes/settings/hotkeys.cpp | 7 +- higan/target-bsnes/settings/input.cpp | 7 +- higan/target-bsnes/settings/settings.cpp | 9 +- higan/target-bsnes/settings/settings.hpp | 6 +- higan/target-bsnes/tools/cheat-editor.cpp | 15 +- higan/target-bsnes/tools/manifest-viewer.cpp | 54 +++- higan/target-bsnes/tools/state-manager.cpp | 166 +++++----- higan/target-bsnes/tools/tools.cpp | 11 +- higan/target-bsnes/tools/tools.hpp | 41 ++- hiro/core/core.cpp | 5 +- hiro/core/core.hpp | 298 +----------------- hiro/core/font.hpp | 35 ++ hiro/core/lock.hpp | 2 + hiro/core/object.cpp | 10 - hiro/core/object.hpp | 19 +- hiro/core/shared.hpp | 91 ++---- hiro/core/sizable.hpp | 15 + hiro/core/widget/canvas.cpp | 6 + hiro/core/widget/source-edit.cpp | 20 ++ hiro/core/widget/source-edit.hpp | 28 ++ hiro/core/widget/table-view-cell.cpp | 32 +- hiro/core/widget/table-view-cell.hpp | 32 ++ hiro/core/widget/table-view-column.cpp | 18 +- hiro/core/widget/table-view-column.hpp | 53 ++++ hiro/core/widget/table-view-header.cpp | 63 ---- hiro/core/widget/table-view-item.hpp | 34 ++ hiro/core/widget/table-view.cpp | 89 +++++- hiro/core/widget/table-view.hpp | 71 +++++ hiro/core/widget/widget.hpp | 15 + hiro/extension/list-view.cpp | 4 +- hiro/gtk/platform.cpp | 1 - hiro/gtk/platform.hpp | 1 - hiro/gtk/widget/source-edit.cpp | 11 + hiro/gtk/widget/source-edit.hpp | 2 + hiro/gtk/widget/table-view-cell.cpp | 12 +- hiro/gtk/widget/table-view-column.cpp | 75 ++--- hiro/gtk/widget/table-view-column.hpp | 7 +- hiro/gtk/widget/table-view-header.cpp | 41 --- hiro/gtk/widget/table-view-header.hpp | 18 -- hiro/gtk/widget/table-view.cpp | 255 ++++++++------- hiro/gtk/widget/table-view.hpp | 18 +- nall/decode/rle.hpp | 46 +++ nall/encode/bmp.hpp | 4 +- nall/encode/rle.hpp | 50 +++ nall/function.hpp | 10 +- nall/image.hpp | 151 ++++++++- nall/image/base.hpp | 149 --------- nall/image/core.hpp | 10 + nall/string.hpp | 10 + nall/string/cast.hpp | 14 + nall/string/find.hpp | 9 + nall/string/markup/bml.hpp | 40 +-- nall/string/pascal.hpp | 79 +++++ nall/vector.hpp | 3 + nall/vector/utility.hpp | 13 + 65 files changed, 1387 insertions(+), 1093 deletions(-) create mode 100644 hiro/core/font.hpp create mode 100644 hiro/core/sizable.hpp create mode 100644 hiro/core/widget/source-edit.hpp create mode 100644 hiro/core/widget/table-view-cell.hpp create mode 100644 hiro/core/widget/table-view-column.hpp delete mode 100644 hiro/core/widget/table-view-header.cpp create mode 100644 hiro/core/widget/table-view-item.hpp create mode 100644 hiro/core/widget/table-view.hpp create mode 100644 hiro/core/widget/widget.hpp delete mode 100644 hiro/gtk/widget/table-view-header.cpp delete mode 100644 hiro/gtk/widget/table-view-header.hpp create mode 100644 nall/decode/rle.hpp create mode 100644 nall/encode/rle.hpp delete mode 100644 nall/image/base.hpp create mode 100644 nall/string/pascal.hpp 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]); +} + }