diff --git a/CMakeLists.txt b/CMakeLists.txt index 92c096b..9e28600 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,7 @@ if (APPLE) models/apple/SGCalendarManager.h models/apple/SGCalendarExporter.h models/apple/SGCalendarExporter.mm - ) + models/color.mm) endif () set(MODELS ${MODELS_PLATFORM_FILES} @@ -96,7 +96,9 @@ set(MODELS ${MODELS_PLATFORM_FILES} models/event.cpp models/event.h models/color.cpp - models/color.h) + models/color.h + models/theme.cpp + models/theme.h) set(MODELS_TESTS models/tests/activity_tests.cpp diff --git a/deployment/Info.plist b/deployment/Info.plist index 5f614e0..7bf71da 100644 --- a/deployment/Info.plist +++ b/deployment/Info.plist @@ -53,7 +53,5 @@ Strategr would be able to export activity sessions as events to the Calendar SUFeedURL ${APPCAST_URL} - SUPublicEDKey - G0A4GIM5nGFE2ANNKzogtjCjWHwItD6oy56yL1qJngQ= diff --git a/models/activity.cpp b/models/activity.cpp index 61f8d0b..c4b4f3a 100644 --- a/models/activity.cpp +++ b/models/activity.cpp @@ -4,90 +4,92 @@ #include "activity.h" #include "activityinvalidpropertyexception.h" -const std::vector &stg::activity::default_colors() { - const static std::vector colors = { - {"#FF6562", "Red"}, - {"#FFB700", "Orange"}, - {"#FFD600", "Yellow"}, - {"#A463F2", "Purple"}, - {"#D5008F", "Indigo"}, - {"#19A974", "Green"}, - {"#357EDD", "Blue"}, - {"#000000", "Black"}, - {"#777777", "Gray"} +namespace stg { + auto activity::default_colors() -> const std::vector & { + const static std::vector colors = { + {"#FF6562", "Red"}, + {"#FFB700", "Orange"}, + {"#FFD600", "Yellow"}, + {"#A463F2", "Purple"}, + {"#D5008F", "Indigo"}, + {"#19A974", "Green"}, + {"#357EDD", "Blue"}, + {"#000000", "Black"}, + {"#777777", "Gray"} + }; + + return colors; }; - return colors; -}; + activity::activity(name_t name, color_t color) : _color(std::move(color)) { + if (!is_valid(name)) { + throw empty_name_exception(); + } -stg::activity::activity(name_t name, color_t color) : _color(std::move(color)) { - if (!is_valid(name)) { - throw empty_name_exception(); + _name = std::move(name); } - _name = std::move(name); -} - -bool stg::activity::is_valid(const activity::name_t &name) { - bool white_spaces_only = name.find_first_not_of(" \t\n\v\f\r") == std::string::npos; - return !white_spaces_only; -} - -stg::activity::invalid_property_exception stg::activity::empty_name_exception() { - const auto message = "activity name can't be empty"; - return invalid_property_exception(message); -} - -const stg::activity::name_t &stg::activity::name() const { - return _name; -} - -const stg::activity::color_t &stg::activity::color() const { - return _color; -} - -bool stg::operator==(const stg::activity &lhs, const stg::activity &rhs) { - return lhs.name() == rhs.name() && - lhs.color() == rhs.color(); -} - -bool stg::operator!=(const stg::activity &lhs, const stg::activity &rhs) { - return !(lhs == rhs); -} - -std::ostream &stg::operator<<(std::ostream &os, - const stg::activity &activity) { - os << "activity(" - << activity.name() - << ", " - << activity.color() - << ")"; - - return os; -} - -stg::activity stg::activity::copy_changing_name(const name_t &name) const { - return activity(name, _color); -} - -stg::activity stg::activity::copy_changing_color(const color_t &color) const { - return activity(_name, color); -} - -nlohmann::json stg::activity::to_json() { - nlohmann::json j; - j[keys::name] = this->name(); - j[keys::color] = this->color(); - return j; -} - -stg::activity stg::activity::from_json(const nlohmann::json &j) { - auto name = j[keys::name]; - - stg::color color = activity::default_color; - if (j.count(keys::color) && !j[keys::color].is_null()) { - color = j[keys::color]; + auto activity::is_valid(const activity::name_t &name) -> bool { + bool white_spaces_only = name.find_first_not_of(" \t\n\v\f\r") == std::string::npos; + return !white_spaces_only; } - return activity{name, color}; + auto activity::empty_name_exception() -> activity::invalid_property_exception { + const auto *message = "activity name can't be empty"; + return invalid_property_exception(message); + } + + auto activity::name() const -> const activity::name_t & { + return _name; + } + + auto activity::color() const -> const activity::color_t & { + return _color; + } + + auto operator==(const stg::activity &lhs, const stg::activity &rhs) -> bool { + return lhs.name() == rhs.name() && + lhs.color() == rhs.color(); + } + + auto operator!=(const stg::activity &lhs, const stg::activity &rhs) -> bool { + return !(lhs == rhs); + } + + auto operator<<(std::ostream &os, + const stg::activity &activity) -> std::ostream & { + os << "activity(" + << activity.name() + << ", " + << activity.color() + << ")"; + + return os; + } + + auto activity::with_name(const name_t &name) const -> activity { + return activity(name, _color); + } + + auto activity::with_color(const color_t &color) const -> activity { + return activity(_name, color); + } + + auto activity::to_json() const -> nlohmann::json { + nlohmann::json j; + j[keys::name] = this->name(); + j[keys::color] = this->color(); + return j; + } + + auto activity::from_json(const nlohmann::json &j) -> activity { + auto name = j[keys::name]; + + stg::color color = activity::default_color; + if (j.count(keys::color) && !j[keys::color].is_null()) { + color = j[keys::color]; + } + + return activity{name, color}; + } } \ No newline at end of file diff --git a/models/activity.h b/models/activity.h index 3527968..7ff5c1c 100644 --- a/models/activity.h +++ b/models/activity.h @@ -17,24 +17,49 @@ namespace stg { class invalid_property_exception; static constexpr auto default_color = "#000000"; - static const std::vector &default_colors(); + static auto default_colors() -> const std::vector &; explicit activity(name_t name, color_t color = default_color) noexcept(false); - const name_t &name() const; - const color_t &color() const; + auto name() const -> const name_t &; + auto color() const -> const color_t &; - activity copy_changing_name(const name_t &name) const; - activity copy_changing_color(const color_t &color) const; + auto light_color() const -> color_t { + auto clr = color(); + clr.set_alpha_component(0.15); - friend bool operator==(const activity &lhs, const activity &rhs); - friend bool operator!=(const activity &lhs, const activity &rhs); + return clr; + } - friend std::ostream &operator<<(std::ostream &os, - const activity &activity); + auto desaturated_light_color() const -> color_t { + auto clr = color(); + clr.set_hsl(clr.hue(), 0.3, 0.75); + clr.set_alpha_component(0.2); - nlohmann::json to_json(); - static activity from_json(const nlohmann::json &j); + return clr; + } + + auto desaturated_dark_color() const -> color_t { + auto clr = color_t(); + + if (color().lightness() < 0.2) + clr = color_t(0xffffffff); + + clr.set_alpha_component(0.1); + return clr; + } + + auto with_name(const name_t &name) const -> activity; + auto with_color(const color_t &color) const -> activity; + + friend auto operator==(const activity &lhs, const activity &rhs) -> bool; + friend auto operator!=(const activity &lhs, const activity &rhs) -> bool; + + friend auto operator<<(std::ostream &os, + const activity &activity) -> std::ostream &; + + auto to_json() const -> nlohmann::json; + static auto from_json(const nlohmann::json &j) -> activity; private: name_t _name; @@ -45,9 +70,9 @@ namespace stg { static constexpr auto color = "color"; }; - static invalid_property_exception empty_name_exception(); + static auto empty_name_exception() -> invalid_property_exception; - static bool is_valid(const name_t &name); + static auto is_valid(const name_t &name) -> bool; }; } diff --git a/models/activityinvalidpropertyexception.cpp b/models/activityinvalidpropertyexception.cpp index b86743f..53a3bbb 100644 --- a/models/activityinvalidpropertyexception.cpp +++ b/models/activityinvalidpropertyexception.cpp @@ -5,10 +5,12 @@ #include "activityinvalidpropertyexception.h" #include -const char *stg::activity::invalid_property_exception::what() const noexcept { - return message.c_str(); -} +namespace stg { + activity::invalid_property_exception::invalid_property_exception(std::string message) : + std::exception(), + message(std::move(message)) {} -stg::activity::invalid_property_exception::invalid_property_exception(std::string message) : - std::exception(), - message(std::move(message)) {} + auto activity::invalid_property_exception::what() const noexcept -> const char * { + return message.c_str(); + } +} \ No newline at end of file diff --git a/models/activityinvalidpropertyexception.h b/models/activityinvalidpropertyexception.h index 9f80c34..fcdd0f0 100644 --- a/models/activityinvalidpropertyexception.h +++ b/models/activityinvalidpropertyexception.h @@ -11,7 +11,7 @@ namespace stg { class activity::invalid_property_exception : public std::exception { public: explicit invalid_property_exception(std::string message); - const char *what() const noexcept override; + auto what() const noexcept -> const char * override; private: std::string message; diff --git a/models/activitylist.cpp b/models/activitylist.cpp index 4887a1a..e50804c 100644 --- a/models/activitylist.cpp +++ b/models/activitylist.cpp @@ -8,218 +8,219 @@ #include "activitylist.h" #include "utility.h" -void stg::activity_list::silently_add(const activity &activity) { - if (has(activity)) { - throw already_present_exception(); - } +namespace stg { + void activity_list::silently_add(const activity &activity) { + if (has(activity)) { + throw already_present_exception(); + } - _data.push_back(std::make_shared(activity)); + _data.push_back(std::make_shared(activity)); - if (!search_query.empty()) - search(search_query); -} + if (!search_query.empty()) + search(search_query); + } -void stg::activity_list::add(const activity &activity) { - silently_add(activity); + void activity_list::add(const activity &activity) { + silently_add(activity); - on_change_event(); -} + on_change_event(); + } -void stg::activity_list::silently_remove_at_index(stg::activity_index index) { - _data.erase(_data.begin() + index); + void activity_list::silently_remove_at_index(activity_index_t index) { + _data.erase(_data.begin() + index); - if (!search_query.empty()) - search(search_query); -} + if (!search_query.empty()) + search(search_query); + } -void stg::activity_list::remove_at_index(activity_index index) { - silently_remove_at_index(index); - on_change_event(); -} + void activity_list::remove_at_index(activity_index_t index) { + silently_remove_at_index(index); -void stg::activity_list::silently_edit_at_index(activity_index index, const activity &new_activity) { - if (*_data[index] == new_activity) { - return; + on_change_event(); } - if (has(new_activity)) { - throw already_present_exception(); - } + void activity_list::silently_edit_at_index(activity_index_t index, const activity &new_activity) { + if (*_data[index] == new_activity) { + return; + } - _data[index] = std::make_shared(new_activity); + if (has(new_activity)) { + throw already_present_exception(); + } - if (!search_query.empty()) { - search(search_query); - } -} + _data[index] = std::make_shared(new_activity); -void stg::activity_list::edit_at_index(activity_index index, const activity &new_activity) { - if (*_data[index] == new_activity) { - return; + if (!search_query.empty()) { + search(search_query); + } } - silently_edit_at_index(index, new_activity); + void activity_list::edit_at_index(activity_index_t index, const activity &new_activity) { + if (*_data[index] == new_activity) { + return; + } + + silently_edit_at_index(index, new_activity); - on_change_event(); -} + on_change_event(); + } -bool stg::activity_list::has(const activity &searched_activity) const { - for (const auto &activity : _data) { - if (*activity == searched_activity) { - return true; + auto activity_list::has(const activity &searched_activity) const -> bool { + for (const auto &activity : _data) { + if (*activity == searched_activity) { + return true; + } } + + return false; } - return false; -} + void activity_list::silently_drag(activity_index_t from_index, activity_index_t to_index) { + if (from_index == to_index) { + return; + } -void stg::activity_list::silently_drag(activity_index from_index, activity_index to_index) { - if (from_index == to_index) { - return; + if (from_index > to_index) + std::rotate(_data.rend() - from_index - 1, + _data.rend() - from_index, + _data.rend() - to_index); + else + std::rotate(_data.begin() + from_index, + _data.begin() + from_index + 1, + _data.begin() + to_index + 1); } - if (from_index > to_index) - std::rotate(_data.rend() - from_index - 1, - _data.rend() - from_index, - _data.rend() - to_index); - else - std::rotate(_data.begin() + from_index, - _data.begin() + from_index + 1, - _data.begin() + to_index + 1); -} + void activity_list::drag(activity_index_t from_index, activity_index_t to_index) { + silently_drag(from_index, to_index); -void stg::activity_list::drag(activity_index from_index, activity_index to_index) { - silently_drag(from_index, to_index); + on_change_event(); + } - on_change_event(); -} + auto activity_list::class_print_name() const -> std::string { + return "activity_list"; + } -std::string stg::activity_list::class_print_name() const { - return "activity_list"; -} + activity_list::activity_list(const std::vector &from_vector) { + std::transform(from_vector.begin(), + from_vector.end(), + std::back_inserter(_data), + [](auto &activity) { + return std::make_shared(activity); + }); -stg::activity_list::activity_list(const std::vector &from_vector) { - std::transform(from_vector.begin(), - from_vector.end(), - std::back_inserter(_data), - [](auto &activity) { - return std::make_shared(activity); - }); + if (!search_query.empty()) + search(search_query); + } - if (!search_query.empty()) - search(search_query); -} + activity_list::activity_list(const std::vector> &from_vector) { + std::transform(from_vector.begin(), + from_vector.end(), + std::back_inserter(_data), + [](auto &activity) { + return activity; + }); -stg::activity_list::activity_list(const std::vector> &from_vector) { - std::transform(from_vector.begin(), - from_vector.end(), - std::back_inserter(_data), - [](auto &activity) { - return activity; - }); + if (!search_query.empty()) + search(search_query); + } - if (!search_query.empty()) - search(search_query); -} + auto activity_list::operator[](activity_index_t item_index) const -> const activity & { + return *_data[item_index]; + } -const stg::activity &stg::activity_list::operator[](activity_index item_index) const { - return *_data[item_index]; -} + auto activity_list::at(activity_index_t item_index) const -> stg::activity * { + return _data.at(item_index).get(); + } -stg::activity *stg::activity_list::at(activity_index item_index) const { - return _data.at(item_index).get(); -} + auto activity_list::index_of(const activity *activity) const -> std::optional { + auto it = std::find_if(_data.begin(), _data.end(), [=](auto &a) { + return a.get() == activity; + }); -std::optional -stg::activity_list::index_of(const activity *activity) const { - auto it = std::find_if(_data.begin(), _data.end(), [=](auto &a) { - return a.get() == activity; - }); + if (it == _data.end()) { + return std::nullopt; + } - if (it == _data.end()) { - return std::nullopt; + return std::distance(_data.begin(), it); } - return std::distance(_data.begin(), it); -} + auto activity_list::index_of(const activity &activity) const -> std::optional { + auto it = std::find_if(_data.begin(), + _data.end(), + [&](auto &a) { + return *a == activity; + }); -std::optional -stg::activity_list::index_of(const activity &activity) const { - auto it = std::find_if(_data.begin(), _data.end(), [&](auto &a) { - return *a == activity; - }); + if (it == _data.end()) { + return std::nullopt; + } - if (it == _data.end()) { - return std::nullopt; + return std::distance(_data.begin(), it); } - return std::distance(_data.begin(), it); -} - -bool stg::activity_list::search(std::string query) const { - text::strip_bounding_whitespaces(query); + auto activity_list::search(std::string query) const -> bool { + text::strip_bounding_whitespaces(query); - search_query = query; + search_query = query; - if (query.empty()) { - auto was_updated = !search_results.empty(); - search_results.clear(); + if (query.empty()) { + auto was_updated = !search_results.empty(); + search_results.clear(); - return was_updated; - } + return was_updated; + } - query = text::utf8_fold_case(query); + query = text::utf8_fold_case(query); - activity_list::data_t results; - std::copy_if(begin(), - end(), - std::back_inserter(results), - [&query](auto activity) { - auto name = text::utf8_fold_case(activity->name()); - return name.find(query) != std::string::npos; - }); + data_t results; + std::copy_if(begin(), + end(), + std::back_inserter(results), + [&query](auto activity) { + auto name = text::utf8_fold_case(activity->name()); + return name.find(query) != std::string::npos; + }); - auto was_updated = search_results != results; + auto was_updated = search_results != results; - search_results = results; + search_results = results; - return was_updated; -} + return was_updated; + } -const stg::activity_list::data_t &stg::activity_list::filtered() const { - if (search_query.empty()) - return _data; + auto activity_list::filtered() const -> const stg::activity_list::data_t & { + if (search_query.empty()) + return _data; - return search_results; -} + return search_results; + } -std::optional -stg::activity_list::index_from_filtered(index_t index_in_filtered) const { - auto *activity = filtered().at(index_in_filtered).get(); - return index_of(activity); -} + auto activity_list::index_from_filtered(index_t index_in_filtered) const -> std::optional { + auto *activity = filtered().at(index_in_filtered).get(); + return index_of(activity); + } -std::optional -stg::activity_list::index_in_filtered(index_t activity_index) const { - auto activity = _data[activity_index]; - auto it = std::find(filtered().begin(), - filtered().end(), - activity); + auto activity_list::index_in_filtered(index_t activity_index) const -> std::optional { + auto activity = _data[activity_index]; + auto it = std::find(filtered().begin(), + filtered().end(), + activity); - if (it == filtered().end()) - return std::nullopt; + if (it == filtered().end()) + return std::nullopt; - return std::distance(filtered().begin(), it); -} + return std::distance(filtered().begin(), it); + } -void stg::activity_list::reset_with(data_t data) { - activity_list_base::reset_with(data); + void activity_list::reset_with(data_t data) { + activity_list_base::reset_with(data); - if (!search_query.empty()) - search(search_query); -} + if (!search_query.empty()) + search(search_query); + } -const char *stg::activity_list::already_present_exception::what() const noexcept { - return message; -} + auto activity_list::already_present_exception::what() const noexcept -> const char * { + return message; + } +} \ No newline at end of file diff --git a/models/activitylist.h b/models/activitylist.h index e057bb6..33c9772 100644 --- a/models/activitylist.h +++ b/models/activitylist.h @@ -18,7 +18,7 @@ namespace stg { class strategy; - using activity_index = unsigned int; + using activity_index_t = unsigned int; using activity_list_base = private_list>; class activity_list : @@ -32,20 +32,20 @@ namespace stg { explicit activity_list(const std::vector &from_vector = {}); explicit activity_list(const std::vector> &from_vector); - const activity &operator[](activity_index item_index) const; - activity *at(activity_index item_index) const; + auto operator[](activity_index_t item_index) const -> const activity &; + auto at(activity_index_t item_index) const -> activity *; - std::optional index_of(const activity *activity) const; - std::optional index_of(const activity &activity) const; + auto index_of(const activity *activity) const -> std::optional; + auto index_of(const activity &activity) const -> std::optional; - bool search(std::string query) const; - const activity_list::data_t &filtered() const; - std::optional index_from_filtered(index_t index_in_filtered) const; - std::optional index_in_filtered(index_t activity_index) const; + auto search(std::string query) const -> bool; + auto filtered() const -> const activity_list::data_t &; + auto index_from_filtered(index_t index_in_filtered) const -> std::optional; + auto index_in_filtered(index_t activity_index) const -> std::optional; void reset_with(data_t data) override; - std::string class_print_name() const override; + auto class_print_name() const -> std::string override; private: friend strategy; @@ -55,21 +55,21 @@ namespace stg { void silently_add(const activity &activity) noexcept(false); void add(const activity &activity) noexcept(false); - void silently_remove_at_index(activity_index index); - void remove_at_index(activity_index index); + void silently_remove_at_index(activity_index_t index); + void remove_at_index(activity_index_t index); - void silently_edit_at_index(activity_index index, const activity &new_activity) noexcept(false); - void edit_at_index(activity_index index, const activity &new_activity) noexcept(false); + void silently_edit_at_index(activity_index_t index, const activity &new_activity) noexcept(false); + void edit_at_index(activity_index_t index, const activity &new_activity) noexcept(false); - void silently_drag(activity_index from_index, activity_index to_index); - void drag(activity_index from_index, activity_index to_index); + void silently_drag(activity_index_t from_index, activity_index_t to_index); + void drag(activity_index_t from_index, activity_index_t to_index); - bool has(const activity &searched_activity) const; + auto has(const activity &searched_activity) const -> bool; }; class activity_list::already_present_exception : public std::exception { static constexpr auto message = "this activity already exists"; - const char *what() const noexcept override; + auto what() const noexcept -> const char * override; }; } diff --git a/models/apple/SGCalendarExporter.mm b/models/apple/SGCalendarExporter.mm index f1ff77d..7638339 100644 --- a/models/apple/SGCalendarExporter.mm +++ b/models/apple/SGCalendarExporter.mm @@ -113,7 +113,7 @@ - (void)exportSession:(stg::session *)sessionPtr { NSString *calendarTitle = self.calendarManager.calendarName; if (!calendarTitle) { calendarTitle = [NSString stringWithUTF8String:session.activity->name().c_str()]; - color = CGColorCreateWithHexColorString([NSString stringWithUTF8String:session.activity->color()]); + color = session.activity->color().to_cg_color(); } EKCalendar *calendar = [self.calendarManager findOrCreateCalendarWithTitle:calendarTitle diff --git a/models/color.cpp b/models/color.cpp index e5deb74..fd631d4 100644 --- a/models/color.cpp +++ b/models/color.cpp @@ -2,4 +2,364 @@ // Created by Dmitry Khrykin on 2020-02-18. // +#include +#include +#include +#include +#include +#include + #include "color.h" + +namespace stg { + color::color(uint32_t data) : + data(data) { + } + + color::color(const char *str) : + data(parse_hex_string(str)) { + } + + color::color(const std::string &str) : + data(parse_hex_string(str)) { + } + + auto color::operator=(const std::string &str) -> color & { + data = parse_hex_string(str); + + return *this; + } + + color::operator std::string() const { + return to_hex_string(); + } + + auto operator<<(std::ostream &os, const color &color) -> std::ostream & { + os << color.to_hex_string(); + return os; + } + + auto operator==(const color &lhs, const color &rhs) -> bool { + return lhs.data == rhs.data; + } + + auto operator!=(const color &lhs, const color &rhs) -> bool { + return !(lhs == rhs); + } + + auto color::to_hex_string() const -> std::string { + auto component_string = [](uint8_t value) -> std::string { + std::stringstream stream; + stream << std::hex + << std::setw(2) + << std::left + << std::setfill('0') + << (unsigned) value; + return stream.str(); + }; + + std::string result = "#"; + + if (alpha() < 255) + result += component_string(alpha()); + + result = result + + component_string(red()) + + component_string(green()) + + component_string(blue()); + + return result; + } + + auto color::parse_hex_string(std::string color_string) -> uint32_t { + uint32_t result = 0; + + std::transform(color_string.begin(), + color_string.end(), + color_string.begin(), + tolower); + + color_string = std::regex_replace(color_string, std::regex("^#"), ""); + + if (color_string.length() == 3) + color_string = {color_string[0], + color_string[0], + color_string[1], + color_string[1], + color_string[2], + color_string[2]}; + + + if (color_string.length() == 4) + color_string = {color_string[0], + color_string[0], + color_string[1], + color_string[1], + color_string[2], + color_string[2], + color_string[3], + color_string[3]}; + + while (color_string.length() < 6) + color_string += "0"; + + if (color_string.length() == 6) + color_string = "ff" + color_string; + + std::stringstream stream; + stream << std::hex << color_string; + stream >> result; + + return result; + } + + auto color::alpha() const -> uint8_t { + return static_cast(data >> 24u) & 255u; + } + + auto color::red() const -> uint8_t { + return static_cast(data >> 16u) & 255u; + } + + auto color::green() const -> uint8_t { + return static_cast(data >> 8u) & 255u; + } + + auto color::blue() const -> uint8_t { + return data & 255u; + } + + auto color::red_component() const -> float { + return static_cast(red()) / 255u; + } + + auto color::green_component() const -> float { + return static_cast(green()) / 255u; + } + + auto color::blue_component() const -> float { + return static_cast(blue()) / 255u; + } + + auto color::alpha_component() const -> float { + return static_cast(alpha()) / 255u; + } + + void color::set_alpha(uint8_t value) { + data = (data & 0x00'ff'ff'ffu) | static_cast(value << 24u); + } + + void color::set_red(uint8_t value) { + data = (data & 0xff'00'ff'ffu) | static_cast(value << 16u); + } + + void color::set_green(uint8_t value) { + data = (data & 0xff'ff'00'ffu) | static_cast(value << 8u); + } + + void color::set_blue(uint8_t value) { + data = (data & 0xff'ff'ff'00u) | static_cast(value); + } + + void color::set_red_component(float value) { + set_red(std::round(value * 255u)); + } + + void color::set_green_component(float value) { + set_green(std::round(value * 255u)); + } + + void color::set_blue_component(float value) { + set_blue(std::round(value * 255u)); + } + + void color::set_alpha_component(float value) { + set_alpha(std::round(value * 255u)); + } + + void color::set_hsl(float hue, float saturation, float lightness) { + hue = hue * 360 / 60; + + auto chroma = (1 - std::abs(2 * lightness - 1)) * saturation; + auto x = chroma * (1 - std::abs(std::fmodf(hue, 2) - 1)); + + auto rgb = std::array(); + + if (std::ceilf(hue) == 1) { + rgb = {chroma, x, 0}; + } else if (std::ceilf(hue) == 2) { + rgb = {x, chroma, 0}; + } else if (std::ceilf(hue) == 3) { + rgb = {0, chroma, x}; + } else if (std::ceilf(hue) == 4) { + rgb = {0, x, chroma}; + } else if (std::ceilf(hue) == 5) { + rgb = {x, 0, chroma}; + } else if (std::ceilf(hue) == 6) { + rgb = {chroma, 0, x}; + } + + auto m = lightness - 0.5 * chroma; + std::transform(rgb.begin(), + rgb.end(), + rgb.begin(), [&m](auto &c) { + return c + m; + }); + + set_red_component(rgb[0]); + set_green_component(rgb[1]); + set_blue_component(rgb[2]); + } + + auto color::lightness() const -> float { + auto x_max = std::max({red_component(), + green_component(), + blue_component()}); + + auto x_min = std::min({red_component(), + green_component(), + blue_component()}); + + return 0.5f * (x_max + x_min); + } + + auto color::hue() const -> float { + auto chroma = chroma_range(); + auto value = brightness(); + + auto result = 0.0f; + if (chroma == 0) { + return result; + } else if (value == red_component()) { + auto shift = blue_component() > green_component() ? 6 : 0; + result = 1.0f / 6 * (shift + (green_component() - blue_component()) / chroma); + } else if (value == green_component()) { + result = 1.0f / 6 * (2 + (blue_component() - red_component()) / chroma); + } else if (value == blue_component()) { + result = 1.0f / 6 * (4 + (red_component() - green_component()) / chroma); + } + + return result; + } + + auto color::saturation() const -> float { + if (lightness() == 0 || lightness() == 1) { + return 0; + } else { + auto chroma = chroma_range(); + return chroma / (1 - std::abs(2 * brightness() - chroma - 1)); + } + } + + auto color::brightness() const -> float { + return std::max({red_component(), + green_component(), + blue_component()}); + } + + auto color::chroma_range() const -> float { + auto x_max = brightness(); + auto x_min = std::min({red_component(), + green_component(), + blue_component()}); + return x_max - x_min; + } + + auto color::components() const -> std::array { + return {red_component(), + green_component(), + blue_component(), + alpha_component()}; + } + + void color::blend_with(const color &overlay_color) { + auto new_red_component = red_component() + + (overlay_color.red_component() - red_component()) + * overlay_color.alpha_component(); + + set_red_component(new_red_component); + + auto new_green_component = green_component() + + (overlay_color.green_component() - green_component()) + * overlay_color.alpha_component(); + + set_green_component(new_green_component); + + auto new_blue_component = blue_component() + + (overlay_color.blue_component() - blue_component()) + * overlay_color.alpha_component(); + + set_blue_component(new_blue_component); + } + + color::color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { + set_rgba(red, green, blue, alpha); + } + + void color::set_rgba(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { + data = 0u; + data = data | static_cast(alpha << 24u); + data = data | static_cast(red << 16u); + data = data | static_cast(green << 8u); + data = data | static_cast(blue); + } + + auto color::info() const -> std::string { + std::stringstream s; + s << "color: " << *this << " " + << "rgba(" << (int) red() + << ", " << (int) green() + << ", " << (int) blue() + << "," << (int) alpha() << ") " + << "hslb(" << hue() * 360 + << "°, " << saturation() + << ", " << lightness() + << ", " << brightness() << ")"; + + return s.str(); + } + + auto color::blended_with(const color &overlay_color) const -> color { + auto clr = *this; + clr.blend_with(overlay_color); + + return clr; + } + + auto color::with_alpha_component(float value) const -> color { + auto clr = *this; + clr.set_alpha_component(value); + + return clr; + } + + auto color::with_hsl(float hue, float saturation, float lightness) const -> color { + auto clr = *this; + clr.set_hsl(hue, saturation, lightness); + + return clr; + } + + void color::invert() { + data = ~data; + } + + auto color::inverted() -> color { + auto clr = *this; + clr.invert(); + + return clr; + } + + auto color::clear_color() -> color { + return color(0x00'00'00'00u); + } + + auto color::black_color() -> color { + return color(0xff'00'00'00u); + } + + auto color::white_color() -> color { + return color(0xff'ff'ff'ffu); + } +} diff --git a/models/color.h b/models/color.h index 816c870..f963479 100644 --- a/models/color.h +++ b/models/color.h @@ -7,59 +7,108 @@ #include #include -#include +#include #include +struct CGColor; namespace stg { struct color { - color() { - init("#000000"); - } + template + struct is_qcolor_like : std::false_type { + }; - color(const char *str) { - init(str); - } + template + struct is_qcolor_like().red()), + decltype(std::declval().green()), + decltype(std::declval().blue()), + decltype(std::declval().alpha())> : std::true_type { + }; - color(const std::string &str) { - init(str); - } + explicit color(uint32_t data = 0xff000000); - color &operator=(const std::string &str) { - init(str); - return *this; - } + color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255); - operator const char *() const { - return color_string.c_str(); - } + color(const char *str); + color(const std::string &str); - operator const std::string &() const { - return color_string; + template::value, int> = 0> + color(const T &q_color_like) { + set_rgba(q_color_like.red(), + q_color_like.green(), + q_color_like.blue(), + q_color_like.alpha()); } - private: - friend std::ostream &operator<<(std::ostream &os, const color &color) { - os << color.color_string; - return os; - } + auto operator=(const std::string &str) -> color &; + operator std::string() const; - friend bool operator==(const stg::color &lhs, const stg::color &rhs) { - return lhs.color_string == rhs.color_string; + template::value, int> = 0> + operator T() const { + return T(red(), green(), blue(), alpha()); } - friend bool operator!=(const stg::color &lhs, const stg::color &rhs) { - return !(lhs == rhs); - } + static auto clear_color() -> color; + static auto black_color() -> color; + static auto white_color() -> color; - std::string color_string; + auto red() const -> uint8_t; + auto green() const -> uint8_t; + auto blue() const -> uint8_t; + auto alpha() const -> uint8_t; - void init(const std::string &str) { - color_string = str; - std::transform(color_string.begin(), - color_string.end(), - color_string.begin(), - tolower); - } + auto red_component() const -> float; + auto green_component() const -> float; + auto blue_component() const -> float; + auto alpha_component() const -> float; + + void set_red(uint8_t value); + void set_green(uint8_t value); + void set_blue(uint8_t value); + void set_alpha(uint8_t value); + + void set_red_component(float value); + void set_green_component(float value); + void set_blue_component(float value); + void set_alpha_component(float value); + + void set_rgba(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255); + void set_hsl(float hue, float saturation, float lightness); + auto with_hsl(float hue, float saturation, float lightness) const -> color; + + auto hue() const -> float; + auto saturation() const -> float; + auto lightness() const -> float; + auto brightness() const -> float; + + void blend_with(const color &overlay_color); + auto blended_with(const color &overlay_color) const -> color; + auto with_alpha_component(float value) const -> color; + + void invert(); + auto inverted() -> color; + + auto components() const -> std::array; + auto info() const -> std::string; + + auto to_cg_color() const -> CGColor *; + static auto from_cg_color(CGColor *cg_color) -> color; + + auto to_hex_string() const -> std::string; + private: + uint32_t data = 0; + + auto chroma_range() const -> float; + + static auto parse_hex_string(std::string color_string) -> uint32_t; + + friend auto operator<<(std::ostream &os, const color &color) -> std::ostream &; + friend auto operator==(const stg::color &lhs, const stg::color &rhs) -> bool; + friend auto operator!=(const stg::color &lhs, const stg::color &rhs) -> bool; }; } diff --git a/models/color.mm b/models/color.mm new file mode 100644 index 0000000..c86f986 --- /dev/null +++ b/models/color.mm @@ -0,0 +1,46 @@ +// +// Created by Dmitry Khrykin on 26/04/2020. +// + +#import +#include + +#include "color.h" + +namespace stg { + auto color::to_cg_color() const -> struct CGColor * { + auto color_space = CGColorSpaceCreateDeviceRGB(); + auto cg_color = CGColorCreate(color_space, components().data()); + CGColorSpaceRelease(color_space); + return cg_color; + } + + auto color::from_cg_color(CGColor *cg_color) -> color { + const auto *components = CGColorGetComponents(cg_color); + auto color_space_model = CGColorSpaceGetModel(CGColorGetColorSpace(cg_color)); + + if (color_space_model != kCGColorSpaceModelRGB + && color_space_model != kCGColorSpaceModelMonochrome) { + auto color_space = CGColorSpaceCreateDeviceRGB(); + cg_color = CGColorCreateCopyByMatchingToColorSpace(color_space, kCGRenderingIntentDefault, cg_color, nil); + color_space_model = kCGColorSpaceModelRGB; + } + + if (color_space_model == kCGColorSpaceModelRGB) { + return color(static_cast(255u * components[0]), + static_cast(255u * components[1]), + static_cast(255u * components[2]), + static_cast(255u * components[3])); + + } else if (color_space_model == kCGColorSpaceModelMonochrome) { + return color(static_cast(255u * components[0]), + static_cast(255u * components[0]), + static_cast(255u * components[0])); + } else { + std::cout << "stg::color Warning: Can't convert from CGColor: unsupported color space\n"; + + return color(); + } + } + +} diff --git a/models/currenttimemarker.cpp b/models/currenttimemarker.cpp index 7152a23..14f2b06 100644 --- a/models/currenttimemarker.cpp +++ b/models/currenttimemarker.cpp @@ -39,15 +39,15 @@ auto stg::current_time_marker::rect_in_parent(const rect &parent_rect, }; } -auto stg::current_time_marker::scroll_offset_in_parent(const rect &parent_rect, - int window_height) const -> int { - auto rect = rect_in_parent(parent_rect); - auto top_offset = rect.top - window_height / 2; +auto stg::current_time_marker::scroll_offset(const rect &slots_rect, + int viewport_height) const -> int { + auto rect = rect_in_parent(slots_rect); + auto top_offset = rect.top - viewport_height / 2; if (top_offset < 0) { top_offset = 0; - } else if (top_offset > parent_rect.height) { - top_offset = parent_rect.height; + } else if (top_offset > slots_rect.height - viewport_height) { + top_offset = slots_rect.height - viewport_height; } return top_offset; diff --git a/models/currenttimemarker.h b/models/currenttimemarker.h index c91a1cf..639b69b 100644 --- a/models/currenttimemarker.h +++ b/models/currenttimemarker.h @@ -20,8 +20,8 @@ namespace stg { auto rect_in_parent(const rect &parent_rect, int marker_radius = 0) const -> rect; - auto scroll_offset_in_parent(const rect &parent_rect, - int window_height) const -> int; + auto scroll_offset(const rect &slots_rect, + int viewport_height) const -> int; private: const strategy &strategy; diff --git a/models/dragoperation.cpp b/models/dragoperation.cpp index bb92f14..39d1ffe 100644 --- a/models/dragoperation.cpp +++ b/models/dragoperation.cpp @@ -5,334 +5,325 @@ #include "dragoperation.h" #include "strategy.h" -stg::drag_operation::drag_operation(time_slots_state *time_slots, indices_vector initial_indices) - : time_slots(time_slots), - initial_dragged_indices(std::move(initial_indices)) { -} - -std::vector -stg::drag_operation::record_drag(const std::vector &time_slots_to_drag, int distance) { - if (distance == 0) { - return {}; +namespace stg { + drag_operation::drag_operation(time_slots_state *time_slots, indices_vector initial_indices) + : time_slots(time_slots), + initial_dragged_indices(std::move(initial_indices)) { } - auto range_to_drag = indices_range{*time_slots->index_of(time_slots_to_drag.front()), - *time_slots->index_of(time_slots_to_drag.back())}; - - // Drag operation_type is divided into two phases: - // 1. Drag selected slots to their new positions, switching the nearby slots; - auto new_dragged_indices = silently_drag(range_to_drag, distance); - // 2. Try to restore nearby sessions' initial positions. - invalidate_drag(new_dragged_indices); + auto drag_operation::record_drag(const std::vector &time_slots_to_drag, + int distance) -> std::vector { + if (distance == 0) { + return {}; + } - return new_dragged_indices; -} + auto range_to_drag = indices_range{*time_slots->index_of(time_slots_to_drag.front()), + *time_slots->index_of(time_slots_to_drag.back())}; -stg::drag_operation::indices_vector -stg::drag_operation::silently_drag(const indices_range &range_to_drag, int distance) { - auto destination_index = distance < 0 - ? range_to_drag.first + distance - : range_to_drag.last + distance; + // Drag operation_type is divided into two phases: + // 1. Drag selected slots to their new positions, switching the nearby slots; + auto new_dragged_indices = silently_drag(range_to_drag, distance); + // 2. Try to restore nearby sessions' initial positions. + invalidate_drag(new_dragged_indices); - if (destination_index > time_slots->size() - 1) { - return {}; + return new_dragged_indices; } - auto[cache_range, restore_cache_range, new_drag_range] - = get_ranges(range_to_drag, destination_index, distance); - - auto cache = make_cache(cache_range); + auto drag_operation::silently_drag(const indices_range &range_to_drag, + int distance) -> indices_vector { + auto destination_index = distance < 0 + ? range_to_drag.first + distance + : range_to_drag.last + distance; - movements_state movements; - indices_vector new_dragged_indices; + if (destination_index > time_slots->size() - 1) { + return {}; + } - for (auto i = 0; i < range_to_drag.size(); i++) { - auto insert_at_index = new_drag_range.first + i; - auto old_index = range_to_drag.first + i; + auto[cache_range, restore_cache_range, new_drag_range] + = get_ranges(range_to_drag, destination_index, distance); - movements[old_index] = insert_at_index; + auto cache = make_cache(cache_range); - new_dragged_indices.push_back(insert_at_index); + movements_state movements; + indices_vector new_dragged_indices; - auto activity_at_old_index = time_slots->at(old_index).activity; - time_slots->silently_set_activity_at_index(insert_at_index, - activity_at_old_index); - } + for (auto i = 0; i < range_to_drag.size(); i++) { + auto insert_at_index = new_drag_range.first + i; + auto old_index = range_to_drag.first + i; - restore_cache(restore_cache_range, cache, movements); + movements[old_index] = insert_at_index; - apply_movements_to_history(movements); + new_dragged_indices.push_back(insert_at_index); - return new_dragged_indices; -} + auto activity_at_old_index = time_slots->at(old_index).activity; + time_slots->silently_set_activity_at_index(insert_at_index, + activity_at_old_index); + } -void stg::drag_operation::restore_cache(const indices_range &restore_cache_range, - const indices_cache &cache, - movements_state &movements) const { - for (size_t i = 0; i < cache.size(); i++) { - auto insert_at_index = static_cast(restore_cache_range.first + i); - auto[history_index, activity] = cache[i]; + restore_cache(restore_cache_range, cache, movements); - movements[history_index] = insert_at_index; + apply_movements_to_history(movements); - time_slots->silently_set_activity_at_index(insert_at_index, activity); - } -} - -std::tuple< - stg::drag_operation::indices_range, - stg::drag_operation::indices_range, - stg::drag_operation::indices_range -> -stg::drag_operation::get_ranges(const indices_range &indices_to_drag, - index_t destination_index, - int distance) const { - auto cache_range = get_cache_range(indices_to_drag, - destination_index, - distance); - - auto restore_cache_first_index = distance < 0 - ? destination_index + indices_to_drag.size() - : indices_to_drag.first; - - auto restore_cache_range = indices_range{restore_cache_first_index, - restore_cache_first_index + cache_range.size()}; - - auto new_drag_range = get_new_dragging_indices(indices_to_drag, - destination_index, - distance); - - return std::make_tuple(cache_range, restore_cache_range, new_drag_range); -} - -stg::drag_operation::indices_range -stg::drag_operation::get_new_dragging_indices(const indices_range &dragging_indices, - index_t destination_index, - int distance) const { - auto new_first_index = distance < 0 - ? destination_index - : destination_index - dragging_indices.size() + 1; - - return indices_range{new_first_index, - new_first_index + dragging_indices.size()}; -} - -stg::drag_operation::indices_range -stg::drag_operation::get_cache_range(const indices_range &dragging_indices, - index_t destination_index, - int distance) const { - return distance < 0 - ? indices_range{destination_index, - dragging_indices.first - 1} - : indices_range{dragging_indices.last + 1, - destination_index}; -} - -stg::drag_operation::indices_cache -stg::drag_operation::make_cache(indices_range cache_indices) const { - indices_cache cache = {}; - - for (auto i = cache_indices.first; i <= cache_indices.last; i++) { - auto cache_entry = std::make_tuple(i, time_slots->at(i).activity); - cache.push_back(cache_entry); + return new_dragged_indices; } - return cache; -} + void drag_operation::restore_cache(const indices_range &restore_cache_range, + const indices_cache &cache, + movements_state &movements) const { + for (size_t i = 0; i < cache.size(); i++) { + auto insert_at_index = static_cast(restore_cache_range.first + i); + auto[history_index, activity] = cache[i]; -void stg::drag_operation::invalidate_drag(const indices_vector &dragged_indices) { - if (dragged_indices.empty()) { - return; + movements[history_index] = insert_at_index; + + time_slots->silently_set_activity_at_index(insert_at_index, activity); + } } - auto nobody_can_move = false; + auto drag_operation::get_ranges(const indices_range &indices_to_drag, + index_t destination_index, + int distance) const -> ranges_tuple { + auto cache_range = get_cache_range(indices_to_drag, + destination_index, + distance); - while (!nobody_can_move) { - auto cant_move_count = 0; + auto restore_cache_first_index = distance < 0 + ? destination_index + indices_to_drag.size() + : indices_to_drag.first; - for (auto const&[current_index, past_indices] : history) { - auto slot_is_dragged = std::find(dragged_indices.begin(), - dragged_indices.end(), - current_index) != dragged_indices.end(); + auto restore_cache_range = indices_range{restore_cache_first_index, + restore_cache_first_index + cache_range.size()}; - auto slot_is_empty = time_slots->at(current_index).activity == strategy::no_activity; + auto new_drag_range = get_new_dragging_indices(indices_to_drag, + destination_index, + distance); - if (slot_is_dragged || slot_is_empty) { - cant_move_count++; - continue; - } + return std::make_tuple(cache_range, restore_cache_range, new_drag_range); + } - auto session_range = find_session_range_for(current_index); + auto drag_operation::get_new_dragging_indices(const indices_range &dragging_indices, + index_t destination_index, + int distance) -> indices_range { + auto new_first_index = distance < 0 + ? destination_index + : destination_index - dragging_indices.size() + 1; - auto initial_session_begin_index = get_initial(session_range.first); + return indices_range{new_first_index, + new_first_index + dragging_indices.size()}; + } - if (initial_session_begin_index != session_range.first) { - auto can_move_to = find_avaliable_movement_index(session_range, - initial_session_begin_index); + auto drag_operation::get_cache_range(const indices_range &dragging_indices, + index_t destination_index, + int distance) -> indices_range { + return distance < 0 + ? indices_range{destination_index, + dragging_indices.first - 1} + : indices_range{dragging_indices.last + 1, + destination_index}; + } - if (can_move_to != session_range.first) { - auto distance = can_move_to - session_range.first; - silently_drag(session_range, distance); - break; - } - } + auto drag_operation::make_cache(indices_range cache_indices) const -> indices_cache { + indices_cache cache = {}; - cant_move_count++; + for (auto i = cache_indices.first; i <= cache_indices.last; i++) { + auto cache_entry = std::make_tuple(i, time_slots->at(i).activity); + cache.push_back(cache_entry); } - if (cant_move_count == history.size()) { - nobody_can_move = true; - } + return cache; } -} -stg::drag_operation::index_t -stg::drag_operation::get_initial(index_t index) { - return history.count(index) - ? history[index][initial_index_key] - : index; -} + void stg::drag_operation::invalidate_drag(const indices_vector &dragged_indices) { + if (dragged_indices.empty()) { + return; + } -void stg::drag_operation::print_indices(const std::string &name, - const indices_vector &indices_state) { - std::cout << name << ": [ "; + auto nobody_can_move = false; - for (auto index : indices_state) { - std::cout << index << " "; - } + while (!nobody_can_move) { + auto cant_move_count = 0; - std::cout << "]" << std::endl; -} + for (auto const&[current_index, past_indices] : history) { + auto slot_is_dragged = std::find(dragged_indices.begin(), + dragged_indices.end(), + current_index) != dragged_indices.end(); -void stg::drag_operation::print_history(const std::string &name, - const history_state &history_state) { - std::cout << name << std::endl; - for (auto const&[current_index, past_indices] : history_state) { - if (!past_indices.empty()) { - print_indices(std::to_string(current_index), past_indices); - } - } -} + auto slot_is_empty = time_slots->at(current_index).activity == strategy::no_activity; -void stg::drag_operation::print_movements(const movements_state &movements) { - std::cout << "movements:" << std::endl; - for (auto const&[past_index, current_index] : movements) { - std::cout << past_index << " -> " << current_index << std::endl; - } -} + if (slot_is_dragged || slot_is_empty) { + cant_move_count++; + continue; + } -void stg::drag_operation::apply_movements_to_history(const movements_state &movements) { - history_state new_history_stacks; + auto session_range = find_session_range_for(current_index); - for (auto const&[past_index, current_index] : movements) { - auto new_history_stack = history[past_index]; - new_history_stack.push_back(past_index); + auto initial_session_begin_index = get_initial(session_range.first); - new_history_stacks[current_index] = new_history_stack; - } + if (initial_session_begin_index != session_range.first) { + auto can_move_to = find_avaliable_movement_index(session_range, + initial_session_begin_index); - for (auto const&[index, stack] : new_history_stacks) { - if (index == stack.front()) { - history.erase(index); - } else { - indices_vector history_entry{stack.front()}; + if (can_move_to != session_range.first) { + auto distance = can_move_to - session_range.first; + silently_drag(session_range, distance); + break; + } + } - // history entry doesn't need more than two values: - // only initial index and previous index if they're different - if (stack.back() != stack.front()) { - history_entry.push_back(stack.back()); + cant_move_count++; } - history[index] = history_entry; + if (cant_move_count == history.size()) { + nobody_can_move = true; + } } } -} - -stg::drag_operation::indices_range -stg::drag_operation::find_session_range_for(index_t time_slot_index) { - if (time_slot_index < 0) - time_slot_index = 0; - if (time_slot_index > time_slots->size() - 1) { - time_slot_index = time_slots->size() - 1; + + auto drag_operation::get_initial(index_t index) -> index_t { + return history.count(index) + ? history[index][initial_index_key] + : index; } - auto begin_index = time_slot_index; - auto end_index = time_slot_index; + void drag_operation::print_indices(const std::string &name, + const indices_vector &indices_state) { + std::cout << name << ": [ "; - for (auto i = time_slot_index; i > 0; i--) { - if (time_slots->at(i).activity == time_slots->at(time_slot_index).activity) { - begin_index = i; - } else { - break; + for (auto index : indices_state) { + std::cout << index << " "; } + + std::cout << "]" << std::endl; } - for (auto i = time_slot_index; i < time_slots->size(); i++) { - if (time_slots->at(i).activity == time_slots->at(time_slot_index).activity) { - end_index = i; - } else { - break; + void drag_operation::print_history(const std::string &name, + const history_state &history_state) { + std::cout << name << std::endl; + for (auto const&[current_index, past_indices] : history_state) { + if (!past_indices.empty()) { + print_indices(std::to_string(current_index), past_indices); + } } } - return indices_range{begin_index, end_index}; -} + void drag_operation::print_movements(const movements_state &movements) { + std::cout << "movements:" << std::endl; + for (auto const&[past_index, current_index] : movements) { + std::cout << past_index << " -> " << current_index << std::endl; + } + } -stg::drag_operation::index_t -stg::drag_operation::find_avaliable_movement_index(indices_range session_range, - index_t target_index) { - index_t result = session_range.first; + void drag_operation::apply_movements_to_history(const movements_state &movements) { + history_state new_history_stacks; - auto wants_up = session_range.first > target_index; - auto wants_down = session_range.first < target_index; + for (auto const&[past_index, current_index] : movements) { + auto new_history_stack = history[past_index]; + new_history_stack.push_back(past_index); - if (wants_up) { - if (session_range.first == 0) { - return result; + new_history_stacks[current_index] = new_history_stack; } - for (auto i = session_range.first - 1; i >= target_index; i--) { - if (time_slots->at(i).activity == strategy::no_activity || - time_slots->at(i).activity == time_slots->at(session_range.first).activity) { - result = i; + for (auto const&[index, stack] : new_history_stacks) { + if (index == stack.front()) { + history.erase(index); } else { - break; + indices_vector history_entry{stack.front()}; + + // history entry doesn't need more than two values: + // only initial index and previous index if they're different + if (stack.back() != stack.front()) { + history_entry.push_back(stack.back()); + } + + history[index] = history_entry; } } } - if (wants_down) { - if (session_range.last == time_slots->size() - 1 || - target_index + session_range.size() >= time_slots->size()) { - return result; + auto drag_operation::find_session_range_for(index_t time_slot_index) -> indices_range { + if (time_slot_index < 0) + time_slot_index = 0; + if (time_slot_index > time_slots->size() - 1) { + time_slot_index = time_slots->size() - 1; + } + + auto begin_index = time_slot_index; + auto end_index = time_slot_index; + + for (auto i = time_slot_index; i > 0; i--) { + if (time_slots->at(i).activity == time_slots->at(time_slot_index).activity) { + begin_index = i; + } else { + break; + } } - for (auto i = session_range.last + 1; i <= target_index + session_range.size() - 1; i++) { - if (time_slots->at(i).activity == strategy::no_activity || - time_slots->at(i).activity == time_slots->at(session_range.last).activity) { - result = i - session_range.size() + 1; + for (auto i = time_slot_index; i < time_slots->size(); i++) { + if (time_slots->at(i).activity == time_slots->at(time_slot_index).activity) { + end_index = i; } else { break; } } + + return indices_range{begin_index, end_index}; } - return result; -} + auto drag_operation::find_avaliable_movement_index(indices_range session_range, + index_t target_index) -> index_t { + index_t result = session_range.first; + + auto wants_up = session_range.first > target_index; + auto wants_down = session_range.first < target_index; + + if (wants_up) { + if (session_range.first == 0) { + return result; + } + + for (auto i = session_range.first - 1; i >= target_index; i--) { + if (time_slots->at(i).activity == strategy::no_activity || + time_slots->at(i).activity == time_slots->at(session_range.first).activity) { + result = i; + } else { + break; + } + } + } + + if (wants_down) { + if (session_range.last == time_slots->size() - 1 || + target_index + session_range.size() >= time_slots->size()) { + return result; + } + + for (auto i = session_range.last + 1; i <= target_index + session_range.size() - 1; i++) { + if (time_slots->at(i).activity == strategy::no_activity || + time_slots->at(i).activity == time_slots->at(session_range.last).activity) { + result = i - session_range.size() + 1; + } else { + break; + } + } + } + + return result; + } -bool stg::drag_operation::state_changed() { - return time_slots->data() != initial_time_slots; -} + auto drag_operation::state_changed() -> bool { + return time_slots->data() != initial_time_slots; + } -stg::time_slots_state::data_t &stg::drag_operation::initial_state() { - return initial_time_slots; -} + auto drag_operation::initial_state() -> time_slots_state::data_t & { + return initial_time_slots; + } -stg::drag_operation::index_t stg::drag_operation::indices_range::size() const { - return last - first + 1; -} + drag_operation::indices_range::indices_range(index_t frst, index_t lst) { + first = frst < 0 ? 0 : frst; + last = lst < 0 ? 0 : lst; + } -stg::drag_operation::indices_range::indices_range(index_t frst, index_t lst) { - first = frst < 0 ? 0 : frst; - last = lst < 0 ? 0 : lst; -} + auto drag_operation::indices_range::size() const -> index_t { + return last - first + 1; + } +} \ No newline at end of file diff --git a/models/dragoperation.h b/models/dragoperation.h index add3177..78e7c04 100644 --- a/models/dragoperation.h +++ b/models/dragoperation.h @@ -33,30 +33,32 @@ namespace stg { explicit drag_operation(time_slots_state *time_slots, indices_vector initial_indices); - std::vector record_drag(const std::vector &time_slots_to_drag, - int distance); + auto record_drag(const std::vector &time_slots_to_drag, + int distance) -> std::vector; - bool state_changed(); + auto state_changed() -> bool; - time_slots_state::data_t &initial_state(); + auto initial_state() -> time_slots_state::data_t &; private: - static const unsigned int initial_index_key = 0; - struct indices_range { indices_range(index_t frst, index_t lst); index_t first = 0; index_t last = 0; - index_t size() const; + auto size() const -> index_t; - friend std::ostream &operator<<(std::ostream &os, - const indices_range &r) { + friend auto operator<<(std::ostream &os, + const indices_range &r) -> std::ostream & { os << "[" << r.first << ", " << r.last << "]"; return os; } }; + using ranges_tuple = std::tuple; + + static const unsigned int initial_index_key = 0; + time_slots_state *time_slots; time_slots_state::data_t initial_time_slots = time_slots->data(); @@ -64,44 +66,41 @@ namespace stg { history_state history = history_state(); - indices_vector silently_drag(const indices_range &range_to_drag, - int distance); + auto silently_drag(const indices_range &range_to_drag, + int distance) -> indices_vector; void restore_cache(const indices_range &restore_cache_range, const indices_cache &cache, movements_state &movements) const; - std::tuple - get_ranges(const indices_range &indices_to_drag, - index_t destination_index, - int distance) const; + auto get_ranges(const indices_range &indices_to_drag, + index_t destination_index, + int distance) const -> ranges_tuple; - indices_range get_new_dragging_indices(const indices_range &dragging_indices, - index_t destination_index, - int distance) const; + static auto get_new_dragging_indices(const indices_range &dragging_indices, + index_t destination_index, + int distance) -> indices_range; - indices_range get_cache_range(const indices_range &dragging_indices, - index_t destination_index, - int distance) const; + static auto get_cache_range(const indices_range &dragging_indices, + index_t destination_index, + int distance) -> indices_range; - indices_cache make_cache(indices_range cache_indices) const; + auto make_cache(indices_range cache_indices) const -> indices_cache; void apply_movements_to_history(const movements_state &movements); void invalidate_drag(const indices_vector &new_dragged_indices); - indices_range find_session_range_for(index_t time_slot_index); - index_t find_avaliable_movement_index(indices_range session_range, - index_t target_index); + auto find_session_range_for(index_t time_slot_index) -> indices_range; + auto find_avaliable_movement_index(indices_range session_range, + index_t target_index) -> index_t; - index_t get_initial(index_t index); + auto get_initial(index_t index) -> index_t; + static void print_history(const std::string &name, + const history_state &history_state); static void print_indices(const std::string &name, const indices_vector &indices_state); - - - void print_history(const std::string &name, - const history_state &history_state); static void print_movements(const movements_state &movements); }; } diff --git a/models/event.cpp b/models/event.cpp index a5c75b0..fc35625 100644 --- a/models/event.cpp +++ b/models/event.cpp @@ -14,15 +14,15 @@ stg::mouse_event::mouse_event(const stg::point &position, position(position) { } -bool stg::event::has_only(stg::event::key_modifiers mod) const { +auto stg::event::has_only(stg::event::key_modifiers mod) const -> bool { return modifiers == mod; } -bool stg::event::with(stg::event::key_modifiers mod) const { +auto stg::event::with(stg::event::key_modifiers mod) const -> bool { return (modifiers & mod) == mod; } -std::ostream &stg::operator<<(std::ostream &os, const stg::mouse_event &e) { +auto stg::operator<<(std::ostream &os, const stg::mouse_event &e) -> std::ostream & { os << "mouse_event {\n"; os << " position: [" << e.position.x << ", " << e.position.y << "]\n"; os << " modifiers: ["; diff --git a/models/event.h b/models/event.h index 3bb976d..25f9fc1 100644 --- a/models/event.h +++ b/models/event.h @@ -9,7 +9,7 @@ namespace stg { struct event { - using key_modifiers = uint8_t; + using key_modifiers = uint16_t; static constexpr key_modifiers left_key = 1u << 0u; static constexpr key_modifiers right_key = 1u << 1u; @@ -34,13 +34,15 @@ namespace stg { } } - bool has_only(key_modifiers mod) const; - bool with(key_modifiers mod) const; + auto has_only(key_modifiers mod) const -> bool; + auto with(key_modifiers mod) const -> bool; key_modifiers modifiers = 0; }; struct mouse_event : public event { + point position; + explicit mouse_event(const point &position, key_modifiers modifiers = left_key); template @@ -52,8 +54,7 @@ namespace stg { } } - friend std::ostream &operator<<(std::ostream &os, const mouse_event &e); - point position; + friend auto operator<<(std::ostream &os, const mouse_event &e) -> std::ostream &; }; }; diff --git a/models/geometry.h b/models/geometry.h index 60708d3..6d7f167 100644 --- a/models/geometry.h +++ b/models/geometry.h @@ -81,11 +81,11 @@ namespace stg { static_cast(y)}; } - point operator+(const point &other) const { + auto operator+(const point &other) const -> point { return point{x + other.x, y + other.y}; } - point operator-(const point &other) const { + auto operator-(const point &other) const -> point { return point{x - other.x, y - other.y}; } @@ -107,7 +107,7 @@ namespace stg { constexpr rect(std::initializer_list l = {}) noexcept { if (l.size() == 4) { - auto it = l.begin(); + const auto *it = l.begin(); left = *it++; top = *it++; width = *it++; @@ -145,7 +145,7 @@ namespace stg { }}; } - point origin() { + auto origin() const -> point { return point{left, top}; } @@ -153,18 +153,18 @@ namespace stg { return *this != rect::zero; } - friend bool operator==(const rect &lhs, const rect &rhs) { + friend auto operator==(const rect &lhs, const rect &rhs) -> bool { return lhs.left == rhs.left && lhs.top == rhs.top && lhs.width == rhs.width && lhs.height == rhs.height; } - friend bool operator!=(const rect &lhs, const rect &rhs) { + friend auto operator!=(const rect &lhs, const rect &rhs) -> bool { return !(lhs == rhs); } - friend std::ostream &operator<<(std::ostream &os, const rect &r) { + friend auto operator<<(std::ostream &os, const rect &r) -> std::ostream & { os << "rect [ " << r.left << " " << r.top << " " diff --git a/models/json.cpp b/models/json.cpp index 1f53fec..db639aa 100644 --- a/models/json.cpp +++ b/models/json.cpp @@ -7,102 +7,103 @@ #include "json.h" #include "strategy.h" -std::string stg::json::serialize(const strategy &strategy) { - auto json = nlohmann::json(); - - json[keys::slot_duration] = strategy.time_slot_duration(); - json[keys::start_time] = strategy.begin_time(); - - std::transform(strategy.activities().begin(), - strategy.activities().end(), - std::back_inserter(json[keys::activities]), - [](auto activity) { - return activity->to_json(); - }); - - std::transform(strategy.time_slots().begin(), - strategy.time_slots().end(), - std::back_inserter(json[keys::slots]), - [&strategy](auto &time_slot) { - auto activity_index = strategy - .activities() - .index_of(time_slot.activity); - - if (activity_index) { - return nlohmann::json(*activity_index); - } - - return nlohmann::json(); - }); - - return json.dump(); -} - -std::unique_ptr stg::json::parse(const std::string &json_string) { - try { - auto json = nlohmann::json::parse(json_string); - - auto activities = parse_activities(json); - auto time_slots = parse_time_slots(json, activities); - - return std::make_unique(time_slots, activities); - } catch (...) { - return nullptr; +namespace stg { + auto json::serialize(const strategy &strategy) -> std::string { + auto json = nlohmann::json(); + + json[keys::slot_duration] = strategy.time_slot_duration(); + json[keys::start_time] = strategy.begin_time(); + + std::transform(strategy.activities().begin(), + strategy.activities().end(), + std::back_inserter(json[keys::activities]), + [](auto activity) { + return activity->to_json(); + }); + + std::transform(strategy.time_slots().begin(), + strategy.time_slots().end(), + std::back_inserter(json[keys::slots]), + [&strategy](auto &time_slot) { + auto activity_index = strategy + .activities() + .index_of(time_slot.activity); + + if (activity_index) + return nlohmann::json(*activity_index); + + return nlohmann::json(); + }); + + return json.dump(); } -} -stg::time_slots_state::data_t -stg::json::parse_time_slots(const nlohmann::json &json, - const activity_list::data_t &activities) { - time_slots_state::data_t time_slots_vector; + auto json::parse(const std::string &json_string) -> std::unique_ptr { + try { + auto json = nlohmann::json::parse(json_string); - auto time_slot_duration = strategy::defaults::time_slot_duration; - if (!json[keys::slot_duration].is_null()) - time_slot_duration = json[keys::slot_duration]; + auto activities = parse_activities(json); + auto time_slots = parse_time_slots(json, activities); - auto begin_time = strategy::defaults::begin_time; - if (!json[keys::start_time].is_null()) - begin_time = json[keys::start_time]; + return std::make_unique(time_slots, activities); + } catch (...) { + return nullptr; + } + } + + auto json::parse_time_slots(const nlohmann::json &json, + const activity_list::data_t &activities) -> time_slots_state::data_t { + time_slots_state::data_t time_slots_vector; - if (!json[keys::slots].is_null()) { - for (auto it = json[keys::slots].begin(); - it != json[keys::slots].end(); - ++it) { + auto time_slot_duration = strategy::defaults::time_slot_duration; + if (!json[keys::slot_duration].is_null()) + time_slot_duration = json[keys::slot_duration]; - auto slot_index = it - json[keys::slots].begin(); - auto time_slot_begin_time = static_cast(begin_time + slot_index * time_slot_duration); + auto begin_time = strategy::defaults::begin_time; + if (!json[keys::start_time].is_null()) + begin_time = json[keys::start_time]; - auto time_slot = stg::time_slot(time_slot_begin_time, - time_slot_duration); + if (!json[keys::slots].is_null()) { + for (auto it = json[keys::slots].begin(); + it != json[keys::slots].end(); + ++it) { - if (!it->is_null()) { - auto activity_index = static_cast(*it); + auto slot_index = it - json[keys::slots].begin(); + auto time_slot_begin_time = static_cast(begin_time + + slot_index * time_slot_duration); - try { - auto *activity = activities.at(activity_index).get(); - time_slot.activity = activity; - } catch (const std::out_of_range &) { - // activity is present in time slots, but not present in - // strategy.activities(), so we won't preserve it. + auto time_slot = stg::time_slot(time_slot_begin_time, + time_slot_duration); + + if (!it->is_null()) { + auto activity_index = static_cast(*it); + + try { + auto *activity = activities.at(activity_index).get(); + time_slot.activity = activity; + } catch (const std::out_of_range &) { + // activity is present in time slots, but not present in + // strategy.activities(), so we won't preserve it. + } } - } - time_slots_vector.push_back(time_slot); + time_slots_vector.push_back(time_slot); + } } - } - return time_slots_vector; -} + return time_slots_vector; + } -stg::activity_list::data_t stg::json::parse_activities(const nlohmann::json &json) { - activity_list::data_t activities_vector; + auto json::parse_activities(const nlohmann::json &json) -> activity_list::data_t { + activity_list::data_t activities_vector; - if (!json[keys::activities].is_null()) { - for (const auto &activity_json : json[keys::activities]) { - auto activity = std::make_shared(activity::from_json(activity_json)); - activities_vector.emplace_back(activity); + if (!json[keys::activities].is_null()) { + for (const auto &activity_json : json[keys::activities]) { + auto activity = std::make_shared(activity::from_json(activity_json)); + activities_vector.emplace_back(activity); + } } - } - return activities_vector; -} + return activities_vector; + } +} \ No newline at end of file diff --git a/models/json.h b/models/json.h index 4bc2b39..aba151e 100644 --- a/models/json.h +++ b/models/json.h @@ -16,8 +16,8 @@ namespace stg { class json { public: - static std::string serialize(const strategy &strategy); - static std::unique_ptr parse(const std::string &json_string); + static auto serialize(const strategy &strategy) -> std::string; + static auto parse(const std::string &json_string) -> std::unique_ptr; struct keys { static constexpr auto slot_duration = "slotDuration"; @@ -28,12 +28,9 @@ namespace stg { private: activity_list activities; - static activity_list::data_t - parse_activities(const nlohmann::json &json); - - static time_slots_state::data_t - parse_time_slots(const nlohmann::json &json, - const activity_list::data_t &activities); + static auto parse_activities(const nlohmann::json &json) -> activity_list::data_t; + static auto parse_time_slots(const nlohmann::json &json, + const activity_list::data_t &activities) -> time_slots_state::data_t; }; } diff --git a/models/notifier.cpp b/models/notifier.cpp index 6ada2a0..91c90af 100644 --- a/models/notifier.cpp +++ b/models/notifier.cpp @@ -11,235 +11,236 @@ #include "strategy.h" #include "time_utils.h" -stg::notifier::notifier(const stg::strategy &strategy) : strategy(strategy) { - strategy.add_on_change_callback(this, - &stg::notifier::schedule); +namespace stg { + notifier::notifier(const stg::strategy &strategy) : strategy(strategy) { + strategy.add_on_change_callback(this, + &stg::notifier::schedule); - schedule(); -} + schedule(); + } -void stg::notifier::start_watching() { - is_watching = true; + void notifier::start_watching() { + is_watching = true; - // schedule(); -} + // schedule(); + } -void stg::notifier::stop_watching() { - is_watching = false; + void notifier::stop_watching() { + is_watching = false; - // teardown(); -} + // teardown(); + } -void stg::notifier::schedule() { + void notifier::schedule() { // std::cout << "scheduled notifications: \n"; - std::vector notifications; - for (auto it = strategy.sessions().begin(); it != strategy.sessions().end(); ++it) { - const auto &session = *it; - auto next_it = std::next(it); - - if (session.activity) { - notifications.emplace_back(session, - notification::type::prepare_start); - notifications.emplace_back(session, - notification::type::start); + std::vector notifications; + for (auto it = strategy.sessions().begin(); it != strategy.sessions().end(); ++it) { + const auto &session = *it; + auto next_it = std::next(it); - if (next_it != strategy.sessions().end() && !next_it->activity) { + if (session.activity) { notifications.emplace_back(session, - notification::type::prepare_end); + notification::type::prepare_start); notifications.emplace_back(session, - notification::type::end); + notification::type::start); + + if (next_it != strategy.sessions().end() && !next_it->activity) { + notifications.emplace_back(session, + notification::type::prepare_end); + notifications.emplace_back(session, + notification::type::end); + } } - } - if (next_it == strategy.sessions().end()) { - notifications.emplace_back(session, - notification::type::prepare_strategy_end); - notifications.emplace_back(session, - notification::type::strategy_end); + if (next_it == strategy.sessions().end()) { + notifications.emplace_back(session, + notification::type::prepare_strategy_end); + notifications.emplace_back(session, + notification::type::strategy_end); + } } - } - remove_stale(notifications); + remove_stale(notifications); // for (auto &n : notifications) { // std::cout << n << "\n"; // } - if (on_delete_notifications) - on_delete_notifications(scheduled_identifiers()); + if (on_delete_notifications) + on_delete_notifications(scheduled_identifiers()); - _scheduled_notifications = notifications; + _scheduled_notifications = notifications; - if (on_schedule_notifications) - on_schedule_notifications(_scheduled_notifications); -} + if (on_schedule_notifications) + on_schedule_notifications(_scheduled_notifications); + } -void stg::notifier::remove_stale(std::vector ¬ifications) { - auto current_seconds = stg::time_utils::current_seconds(); - notifications.erase(std::remove_if(notifications.begin(), - notifications.end(), - [current_seconds](const stg::notification ¬ification) { - return notification.delivery_time < current_seconds; - }), notifications.end()); -} + void notifier::remove_stale(std::vector ¬ifications) { + auto current_seconds = stg::time_utils::current_seconds(); + notifications.erase(std::remove_if(notifications.begin(), + notifications.end(), + [current_seconds](const stg::notification ¬ification) { + return notification.delivery_time < current_seconds; + }), notifications.end()); + } -std::string stg::notification::make_string_uuid() { - auto uuid = boost::uuids::random_generator()(); + auto notification::make_string_uuid() -> std::string { + auto uuid = boost::uuids::random_generator()(); - std::stringstream sstream; - sstream << uuid; + std::stringstream sstream; + sstream << uuid; - return sstream.str(); -} + return sstream.str(); + } -const std::vector -&stg::notifier::scheduled_notifications() const { - return _scheduled_notifications; -} + auto notifier::scheduled_notifications() const -> const std::vector & { + return _scheduled_notifications; + } -std::vector stg::notifier::scheduled_identifiers() const { - std::vector result; - std::transform(_scheduled_notifications.begin(), - _scheduled_notifications.end(), - std::back_inserter(result), - [](const auto ¬ification) { - return notification.identifier; - }); + auto notifier::scheduled_identifiers() const -> std::vector { + std::vector result; + std::transform(_scheduled_notifications.begin(), + _scheduled_notifications.end(), + std::back_inserter(result), + [](const auto ¬ification) { + return notification.identifier; + }); - return result; -} + return result; + } -stg::notifier::seconds stg::notifier::immediate_delivery_seconds(stg::notifier::minutes minutes_time) { - return minutes_time * 60 - immediate_seconds_interval; -} + auto notifier::immediate_delivery_seconds(minutes minutes_time) -> seconds { + return minutes_time * 60 - immediate_seconds_interval; + } -stg::notifier::seconds stg::notifier::prepare_delivery_seconds(stg::notifier::minutes minutes_time) { - return minutes_time * 60 - prepare_seconds_interval; -} + auto notifier::prepare_delivery_seconds(minutes minutes_time) -> seconds { + return minutes_time * 60 - prepare_seconds_interval; + } -void stg::notifier::send_now_if_needed(seconds polling_seconds_interval) { + void notifier::send_now_if_needed(seconds polling_seconds_interval) { // std::cout << "send notification, if needed => \n"; - auto current_time = time_utils::current_seconds(); + auto current_time = time_utils::current_seconds(); - if (last_poll_time && current_time - last_poll_time > 4 * polling_seconds_interval) { + if (last_poll_time && current_time - last_poll_time > 4 * polling_seconds_interval) { // std::cout << "system time changed\n"; - // If time difference between two calls of this function was too big, - // this probably means that the system time had changed, we need to reschedule. - schedule(); - } + // If time difference between two calls of this function was too big, + // this probably means that the system time had changed, we need to reschedule. + schedule(); + } - last_poll_time = current_time; + last_poll_time = current_time; - if (_scheduled_notifications.empty() || - current_time < _scheduled_notifications.front().delivery_time) - return; + if (_scheduled_notifications.empty() || + current_time < _scheduled_notifications.front().delivery_time) + return; // std::cout << "current_time: " << current_time << "\n"; // std::cout << "delivery_time: " << next_notification.delivery_time << "\n"; - // We have to send only last notification for which delivery time is less than the current. - // The first is guaranteed to be so, we need to check the others: + // We have to send only last notification for which delivery time is less than the current. + // The first is guaranteed to be so, we need to check the others: - auto next_notification_it = _scheduled_notifications.begin(); - for (auto it = std::next(_scheduled_notifications.begin()); - it != _scheduled_notifications.end(); - ++it) { - auto ¬ification = *it; + auto next_notification_it = _scheduled_notifications.begin(); + for (auto it = std::next(_scheduled_notifications.begin()); + it != _scheduled_notifications.end(); + ++it) { + auto ¬ification = *it; - if (current_time < notification.delivery_time) { - next_notification_it = std::prev(it); - break; + if (current_time < notification.delivery_time) { + next_notification_it = std::prev(it); + break; + } } - } - auto &next_notification = *next_notification_it; + auto &next_notification = *next_notification_it; - if (on_send_notiifcation) - on_send_notiifcation(next_notification); + if (on_send_notiifcation) + on_send_notiifcation(next_notification); // std::cout << "notification sent: " << next_notification << "\n"; - // Remove sent and stale notifications - _scheduled_notifications.erase(_scheduled_notifications.begin(), - std::next(next_notification_it)); -} - -std::string stg::notification::make_title(const session &session, type type) { - if (type == type::prepare_strategy_end || - type == type::strategy_end) { - return "End Of A Strategy"; - } - - if (!session.activity) { - throw std::invalid_argument("session must have an activity for this type of notification"); - } - - return session.activity->name() - + " (" - + stg::time_utils::human_string_from_minutes(session.duration()) - + ")"; -} - -stg::notification::seconds stg::notification::make_delivery_time(const session &session, type type) { - switch (type) { - case type::prepare_start: - return notifier::prepare_delivery_seconds(session.begin_time()); - case type::start: - return notifier::immediate_delivery_seconds(session.begin_time()); - case type::prepare_end: - case type::prepare_strategy_end: - return notifier::prepare_delivery_seconds(session.end_time()); - case type::end: - case type::strategy_end: - return notifier::immediate_delivery_seconds(session.end_time()); - default: - return 0; - } -} - -std::string stg::notification::make_sub_title(const session &session, type type) { - switch (type) { - case type::prepare_start: - return "Coming up in " - + time_utils::human_string_from_minutes(notifier::prepare_seconds_interval / 60); - case type::start: - return "Starts right now"; - case type::prepare_end: - return "Ends in " - + time_utils::human_string_from_minutes(notifier::prepare_seconds_interval / 60); - case type::end: - return "Ends right now"; - case type::prepare_strategy_end: - return "Strategy ends in " - + time_utils::human_string_from_minutes(notifier::prepare_seconds_interval / 60); - case type::strategy_end: - return "Strategy ends right now"; - default: - return ""; - } -} - -stg::notification::notification(const session &session, type type) : - title(make_title(session, type)), - message(make_sub_title(session, type)), - delivery_time(make_delivery_time(session, type)) {} - -std::ostream &stg::operator<<(std::ostream &os, const stg::notification ¬ification) { - os << "notification: [ "; - os << "id: \"" << notification.identifier << "\", "; - os << "title: \"" << notification.title << "\", "; - os << "message: \"" << notification.message << "\", "; - os << "delivery_time: \"" << time_utils::string_from_seconds(notification.delivery_time) << "\""; - os << "]"; - - return os; -} - -bool stg::operator==(const stg::notification &lhs, const stg::notification &rhs) { - // Two notifications are considered equal if all properties other than id are equal, - return lhs.title == rhs.title && - lhs.message == rhs.message && - lhs.delivery_time == rhs.delivery_time; -} + // Remove sent and stale notifications + _scheduled_notifications.erase(_scheduled_notifications.begin(), + std::next(next_notification_it)); + } + + auto notification::make_title(const session &session, type type) -> std::string { + if (type == type::prepare_strategy_end || + type == type::strategy_end) { + return "End Of A Strategy"; + } + + if (!session.activity) { + throw std::invalid_argument("session must have an activity for this type of notification"); + } + + return session.activity->name() + + " (" + + time_utils::human_string_from_minutes(session.duration()) + + ")"; + } + + auto notification::make_delivery_time(const session &session, type type) -> seconds { + switch (type) { + case type::prepare_start: + return notifier::prepare_delivery_seconds(session.begin_time()); + case type::start: + return notifier::immediate_delivery_seconds(session.begin_time()); + case type::prepare_end: + case type::prepare_strategy_end: + return notifier::prepare_delivery_seconds(session.end_time()); + case type::end: + case type::strategy_end: + return notifier::immediate_delivery_seconds(session.end_time()); + default: + return 0; + } + } + + auto notification::make_sub_title(const session &session, type type) -> std::string { + switch (type) { + case type::prepare_start: + return "Coming up in " + + time_utils::human_string_from_minutes(notifier::prepare_seconds_interval / 60); + case type::start: + return "Starts right now"; + case type::prepare_end: + return "Ends in " + + time_utils::human_string_from_minutes(notifier::prepare_seconds_interval / 60); + case type::end: + return "Ends right now"; + case type::prepare_strategy_end: + return "Strategy ends in " + + time_utils::human_string_from_minutes(notifier::prepare_seconds_interval / 60); + case type::strategy_end: + return "Strategy ends right now"; + default: + return ""; + } + } + + notification::notification(const session &session, type type) : + title(make_title(session, type)), + message(make_sub_title(session, type)), + delivery_time(make_delivery_time(session, type)) {} + + auto operator<<(std::ostream &os, const notification ¬ification) -> std::ostream & { + os << "notification: [ "; + os << "id: \"" << notification.identifier << "\", "; + os << "title: \"" << notification.title << "\", "; + os << "message: \"" << notification.message << "\", "; + os << "delivery_time: \"" << time_utils::string_from_seconds(notification.delivery_time) << "\""; + os << "]"; + + return os; + } + + auto operator==(const notification &lhs, const notification &rhs) -> bool { + // Two notifications are considered equal if all properties other than id are equal, + return lhs.title == rhs.title && + lhs.message == rhs.message && + lhs.delivery_time == rhs.delivery_time; + } +} \ No newline at end of file diff --git a/models/notifier.h b/models/notifier.h index cad47b7..814e0fa 100644 --- a/models/notifier.h +++ b/models/notifier.h @@ -36,10 +36,10 @@ namespace stg { seconds delivery_time; private: - static std::string make_string_uuid(); - static std::string make_title(const session &session, type type); - static std::string make_sub_title(const session &session, type type); - static seconds make_delivery_time(const session &session, type type); + static auto make_string_uuid() -> std::string; + static auto make_title(const session &session, type type) -> std::string; + static auto make_sub_title(const session &session, type type) -> std::string; + static auto make_delivery_time(const session &session, type type) -> seconds; friend bool operator==(const notification &lhs, const notification &rhs); @@ -55,8 +55,8 @@ namespace stg { using resetter_t = std::function &)>; using sender_t = std::function; - static seconds immediate_delivery_seconds(minutes minutes_time); - static seconds prepare_delivery_seconds(minutes minutes_time); + static auto immediate_delivery_seconds(minutes minutes_time) -> seconds; + static auto prepare_delivery_seconds(minutes minutes_time) -> seconds; scheduler_t on_schedule_notifications = nullptr; resetter_t on_delete_notifications = nullptr; @@ -70,8 +70,8 @@ namespace stg { void send_now_if_needed(seconds polling_seconds_interval); - const std::vector &scheduled_notifications() const; - std::vector scheduled_identifiers() const; + auto scheduled_notifications() const -> const std::vector &; + auto scheduled_identifiers() const -> std::vector; static const seconds prepare_seconds_interval = 5 * 60; static const seconds immediate_seconds_interval = 20; diff --git a/models/resizeoperation.cpp b/models/resizeoperation.cpp index 41df332..ee81501 100644 --- a/models/resizeoperation.cpp +++ b/models/resizeoperation.cpp @@ -4,21 +4,20 @@ #include "resizeoperation.h" -stg::resize_operation::resize_operation(time_slots_state *time_slots) : - time_slots(time_slots) { -} +namespace stg { + resize_operation::resize_operation(time_slots_state *time_slots) : + time_slots(time_slots) { + } -void stg::resize_operation::fill_slots(time_slots_state::index_t from_index, - time_slots_state::index_t till_index) { - time_slots->fill_slots(from_index, till_index); -} + void resize_operation::fill_slots(index_t from_index, index_t till_index) { + time_slots->fill_slots(from_index, till_index); + } -void stg::resize_operation::fill_slots_shifting(time_slots_state::index_t from_index, - time_slots_state::index_t till_index) { - time_slots->fill_slots_shifting(from_index, till_index); -} + void resize_operation::fill_slots_shifting(index_t from_index, index_t till_index) { + time_slots->fill_slots_shifting(from_index, till_index); + } -bool stg::resize_operation::state_changed() { - return time_slots->data() != initial_time_slots; + auto resize_operation::state_changed() -> bool { + return time_slots->data() != initial_time_slots; + } } - diff --git a/models/resizeoperation.h b/models/resizeoperation.h index c8ef3e6..71124f8 100644 --- a/models/resizeoperation.h +++ b/models/resizeoperation.h @@ -10,15 +10,14 @@ namespace stg { class resize_operation { public: - explicit resize_operation(time_slots_state *time_slots); + using index_t = time_slots_state::index_t; - void fill_slots(time_slots_state::index_t from_index, - time_slots_state::index_t till_index); + explicit resize_operation(time_slots_state *time_slots); - void fill_slots_shifting(time_slots_state::index_t from_index, - time_slots_state::index_t till_index); + void fill_slots(index_t from_index, index_t till_index); + void fill_slots_shifting(index_t from_index, index_t till_index); - bool state_changed(); + auto state_changed() -> bool; private: time_slots_state *time_slots; time_slots_state::data_t initial_time_slots = time_slots->data(); diff --git a/models/selection.cpp b/models/selection.cpp index f3f5d8e..9906c2f 100644 --- a/models/selection.cpp +++ b/models/selection.cpp @@ -100,7 +100,7 @@ bool stg::selection::only_non_empty_selected() const { }) == end(); } -bool stg::selection::has_selected(index_t slot_index) { +bool stg::selection::has_selected(index_t slot_index) const { return std::find(begin(), end(), slot_index) != end(); } @@ -145,12 +145,17 @@ void stg::selection::on_change_event() { reload(); } -const stg::grouped_selection &stg::selection::grouped() { +const stg::grouped_selection &stg::selection::grouped() const { return _grouped; } -bool stg::selection::is_all_selected() { +bool stg::selection::is_all_selected() const { return size() == strategy.number_of_time_slots(); } +bool stg::selection::is_boundary(stg::index_t slot_index) const { + return (!has_selected(slot_index - 1) && has_selected(slot_index)) + || (has_selected(slot_index - 1) && !has_selected(slot_index)); +} + diff --git a/models/selection.h b/models/selection.h index 9668d69..dd1506b 100644 --- a/models/selection.h +++ b/models/selection.h @@ -34,15 +34,17 @@ namespace stg { bool only_empty_selected() const; bool only_non_empty_selected() const; - bool has_selected(index_t slot_index); - bool is_all_selected(); + bool has_selected(index_t slot_index) const; + bool is_all_selected() const; + + bool is_boundary(index_t slot_index) const; void reload(); bool is_clicked() const; void set_is_clicked(bool is_clicked); - const grouped_selection &grouped(); + const grouped_selection &grouped() const; void on_change_event() override; private: diff --git a/models/strategy.cpp b/models/strategy.cpp index 3fb6f79..46715b9 100644 --- a/models/strategy.cpp +++ b/models/strategy.cpp @@ -7,484 +7,503 @@ #include "json.h" #include "time_utils.h" -stg::strategy::strategy(time_t begin_time, duration_t time_slot_duration, - size_t number_of_time_slots) - : _time_slots(time_slots_state(begin_time, - time_slot_duration, - number_of_time_slots)), - - history(make_history_entry()) { - time_slots_changed(); - setup_time_slots_callback(); -} - -stg::strategy::strategy(const time_slots_state::data_t &time_slots, - const activity_list::data_t &activities) : - _time_slots(time_slots), - _activities(activities), - history(make_history_entry()) { - time_slots_changed(); - setup_time_slots_callback(); -} +namespace stg { + strategy::strategy(time_t begin_time, duration_t time_slot_duration, + size_t number_of_time_slots) + : _time_slots(time_slots_state(begin_time, + time_slot_duration, + number_of_time_slots)), + + history(make_history_entry()) { + time_slots_changed(); + setup_time_slots_callback(); + } -stg::strategy::strategy(const strategy &other) : - _time_slots(other._time_slots.data()), - _activities(other._activities.data()), - _sessions(other.sessions().data()), - history(make_history_entry()) { + strategy::strategy(const time_slots_state::data_t &time_slots, + const activity_list::data_t &activities) : + _time_slots(time_slots), + _activities(activities), + history(make_history_entry()) { + time_slots_changed(); + setup_time_slots_callback(); + } - setup_time_slots_callback(); -} + strategy::strategy(const strategy &other) : + _time_slots(other._time_slots.data()), + _activities(other._activities.data()), + _sessions(other.sessions().data()), + history(make_history_entry()) { + setup_time_slots_callback(); + } -stg::strategy &stg::strategy::operator=(const strategy &other) { - _time_slots.reset_with(other.time_slots().data()); - // Note: _time_slots on_change callback is private, - // so we don't call on_change_event(); + strategy &stg::strategy::operator=(const strategy &other) { + _time_slots.reset_with(other.time_slots().data()); + // Note: _time_slots on_change callback is private, + // so we don't call on_change_event(); - _activities.reset_with(other.activities().data()); - _activities.on_change_event(); + _activities.reset_with(other.activities().data()); + _activities.on_change_event(); - _sessions.reset_with(other.sessions().data()); - _sessions.on_change_event(); + _sessions.reset_with(other.sessions().data()); + _sessions.on_change_event(); - history = strategy_history(make_history_entry()); + history = strategy_history(make_history_entry()); - return *this; -} + return *this; + } -const stg::activity_list &stg::strategy::activities() const { - return _activities; -} + auto strategy::activities() const -> const activity_list & { + return _activities; + } -void stg::strategy::silently_add_activity(const activity &activity) { - _activities.silently_add(activity); + void strategy::silently_add_activity(const activity &activity) { + _activities.silently_add(activity); - commit_to_history(); -} + commit_to_history(); + } -void stg::strategy::add_activity(const activity &activity) { - _activities.add(activity); + void strategy::add_activity(const activity &activity) { + _activities.add(activity); - commit_to_history(); -} + commit_to_history(); + } -void stg::strategy::delete_activity(activity_index activity_index) { - _time_slots.remove_activity(_activities.at(activity_index)); - _activities.remove_at_index(activity_index); + void strategy::delete_activity(activity_index_t activity_index) { + _time_slots.remove_activity(_activities.at(activity_index)); + _activities.remove_at_index(activity_index); - commit_to_history(); -} + commit_to_history(); + } -void stg::strategy::silently_delete_activity(activity_index activity_index) { - _time_slots.remove_activity(_activities.at(activity_index)); - _activities.silently_remove_at_index(activity_index); + void strategy::silently_delete_activity(activity_index_t activity_index) { + _time_slots.remove_activity(_activities.at(activity_index)); + _activities.silently_remove_at_index(activity_index); - commit_to_history(); -} + commit_to_history(); + } -void stg::strategy::silently_edit_activity(activity_index activity_index, - const activity &new_activity) { - const auto old_activity = _activities.at(activity_index); - _activities.silently_edit_at_index(activity_index, new_activity); + void strategy::silently_edit_activity(activity_index_t activity_index, + const activity &new_activity) { + const auto old_activity = _activities.at(activity_index); + _activities.silently_edit_at_index(activity_index, new_activity); - const auto updated_activity = _activities.at(activity_index); - _time_slots.edit_activity(old_activity, updated_activity); + const auto updated_activity = _activities.at(activity_index); + _time_slots.edit_activity(old_activity, updated_activity); - commit_to_history(); -} + commit_to_history(); + } -void stg::strategy::edit_activity(activity_index activity_index, - const activity &new_activity) { - const auto old_activity = _activities.at(activity_index); - _activities.edit_at_index(activity_index, new_activity); + void strategy::edit_activity(activity_index_t activity_index, + const activity &new_activity) { + auto *old_activity = _activities.at(activity_index); + _activities.edit_at_index(activity_index, new_activity); - const auto updated_activity = _activities.at(activity_index); - _time_slots.edit_activity(old_activity, updated_activity); + auto *updated_activity = _activities.at(activity_index); + _time_slots.edit_activity(old_activity, updated_activity); - commit_to_history(); -} + commit_to_history(); + } -stg::strategy::time_t stg::strategy::begin_time() const { - return _time_slots.begin_time(); -} + stg::strategy::time_t stg::strategy::begin_time() const { + return _time_slots.begin_time(); + } -void stg::strategy::set_begin_time(time_t begin_time) { - _time_slots.set_begin_time(begin_time); + void strategy::set_begin_time(time_t begin_time) { + _time_slots.set_begin_time(begin_time); - commit_to_history(); -} + commit_to_history(); + } -stg::strategy::duration_t stg::strategy::time_slot_duration() const { - return _time_slots.slot_duration(); -} + auto strategy::time_slot_duration() const -> duration_t { + return _time_slots.slot_duration(); + } -void stg::strategy::set_time_slot_duration(duration_t time_slot_duration) { - _time_slots.set_slot_duration(time_slot_duration); + void strategy::set_time_slot_duration(duration_t time_slot_duration) { + _time_slots.set_slot_duration(time_slot_duration); - commit_to_history(); -} + commit_to_history(); + } -stg::strategy::size_t stg::strategy::number_of_time_slots() const { - return _time_slots.number_of_slots(); -} + auto strategy::number_of_time_slots() const -> size_t { + return _time_slots.number_of_slots(); + } -void stg::strategy::set_number_of_time_slots(size_t number_of_time_slots) { - _time_slots.set_number_of_slots(number_of_time_slots); + void strategy::set_number_of_time_slots(size_t number_of_time_slots) { + _time_slots.set_number_of_slots(number_of_time_slots); - commit_to_history(); -} + commit_to_history(); + } -void stg::strategy::time_slots_changed() { - _sessions.recalculate(_time_slots); -} + void strategy::set_end_time(time_t end_time) { + _time_slots.set_end_time(end_time); -void stg::strategy::place_activity(activity_index activity_index, - const std::vector &time_slot_indices) { - if (!activities().has_index(activity_index)) { - return; + commit_to_history(); } - auto activity = activities().at(activity_index); - _time_slots.set_activity_at_indices(activity, time_slot_indices); + void strategy::time_slots_changed() { + _sessions.recalculate(_time_slots); + } - commit_to_history(); -} + void strategy::place_activity(activity_index_t activity_index, + const std::vector &time_slot_indices) { + if (!activities().has_index(activity_index)) { + return; + } -void stg::strategy::make_empty_at(const std::vector &time_slot_indices) { - _time_slots.set_activity_at_indices(stg::strategy::no_activity, time_slot_indices); + auto activity = activities().at(activity_index); + _time_slots.set_activity_at_indices(activity, time_slot_indices); - commit_to_history(); -} + commit_to_history(); + } -void stg::strategy::drag_activity(activity_index from_index, activity_index to_index) { - _activities.drag(from_index, to_index); + void strategy::make_empty_at(const std::vector &time_slot_indices) { + _time_slots.set_activity_at_indices(stg::strategy::no_activity, + time_slot_indices); - commit_to_history(); -} + commit_to_history(); + } + void strategy::drag_activity(activity_index_t from_index, activity_index_t to_index) { + _activities.drag(from_index, to_index); -void stg::strategy::silently_drag_activity(stg::activity_index from_index, stg::activity_index to_index) { - _activities.silently_drag(from_index, to_index); + commit_to_history(); + } - commit_to_history(); -} -void stg::strategy::fill_time_slots_shifting(time_slot_index_t from_index, time_slot_index_t till_index) { - assert(current_resize_operation && "fill_time_slots must be called between " - "begin_resizing() and end_resizing() calls"); + void strategy::silently_drag_activity(stg::activity_index_t from_index, + stg::activity_index_t to_index) { + _activities.silently_drag(from_index, to_index); - current_resize_operation->fill_slots_shifting(from_index, till_index); -} + commit_to_history(); + } -void stg::strategy::fill_time_slots(time_slot_index_t from_index, time_slot_index_t till_index) { - assert(current_resize_operation && "fill_time_slots must be called between " - "begin_resizing() and end_resizing() calls"); + void strategy::fill_time_slots_shifting(time_slot_index_t from_index, + time_slot_index_t till_index) { + assert(current_resize_operation && "fill_time_slots must be called between " + "begin_resizing() and end_resizing() calls"); - current_resize_operation->fill_slots(from_index, till_index); -} + current_resize_operation->fill_slots_shifting(from_index, till_index); + } -const stg::sessions_list &stg::strategy::sessions() const { - return _sessions; -} + void strategy::fill_time_slots(time_slot_index_t from_index, + time_slot_index_t till_index) { + assert(current_resize_operation && "fill_time_slots must be called between " + "begin_resizing() and end_resizing() calls"); -stg::strategy::time_t stg::strategy::end_time() const { - return _time_slots.last().end_time(); -} + current_resize_operation->fill_slots(from_index, till_index); + } -void stg::strategy::setup_time_slots_callback() { - _time_slots.add_on_change_callback(this, &stg::strategy::time_slots_changed); -} + auto strategy::sessions() const -> const sessions_list & { + return _sessions; + } -stg::strategy_history::entry stg::strategy::make_history_entry() { - return strategy_history::entry{_activities.data(), - _time_slots.data()}; -} + auto strategy::end_time() const -> time_t { + return _time_slots.end_time(); + } -void stg::strategy::commit_to_history() { - if (history.commit(make_history_entry())) - on_change_event(); -} + void strategy::setup_time_slots_callback() { + _time_slots.add_on_change_callback(this, + &strategy::time_slots_changed); + } -void stg::strategy::undo() { - auto history_entry = history.undo(); - if (history_entry) { - apply_history_entry(history_entry); + auto strategy::make_history_entry() -> strategy_history::entry { + return strategy_history::entry{_activities.data(), + _time_slots.data()}; + } - on_change_event(); + void strategy::commit_to_history() { + if (history.commit(make_history_entry())) + on_change_event(); } -} -void stg::strategy::redo() { - auto history_entry = history.redo(); - if (history_entry) { - apply_history_entry(history_entry); + void strategy::undo() { + auto history_entry = history.undo(); + if (history_entry) { + apply_history_entry(history_entry); - on_change_event(); + on_change_event(); + } } -} -void stg::strategy::apply_history_entry(const std::optional &history_entry) { - _activities.reset_with(history_entry->activities); - _activities.on_change_event(); + void strategy::redo() { + auto history_entry = history.redo(); + if (history_entry) { + apply_history_entry(history_entry); - _time_slots.reset_with(history_entry->time_slots); - _time_slots.on_change_event(); -} + on_change_event(); + } + } -const stg::time_slots_state &stg::strategy::time_slots() const { - return _time_slots; -} + void strategy::apply_history_entry(const std::optional &history_entry) { + _activities.reset_with(history_entry->activities); + _activities.on_change_event(); -void stg::strategy::shift_below_time_slot(stg::strategy::time_slot_index_t from_index, int length) { - _time_slots.shift_below(from_index, length); + _time_slots.reset_with(history_entry->time_slots); + _time_slots.on_change_event(); + } - commit_to_history(); -} + auto strategy::time_slots() const -> const time_slots_state & { + return _time_slots; + } -stg::sessions_list::index_t -stg::strategy::drag_session(stg::strategy::session_index_t session_index, - int distance) { - assert(current_drag_operation && "drag_session must be called between " - "begin_dragging() and end_dragging() calls"); + void strategy::shift_below_time_slot(time_slot_index_t from_index, int length) { + _time_slots.shift_below(from_index, length); - if (session_index < 0 || - session_index > sessions().size() - 1 || - distance == 0) { - return session_index; + commit_to_history(); } - const auto &session = sessions()[session_index]; - if (session.activity == stg::strategy::no_activity) { - return session_index; - } + auto strategy::drag_session(session_index_t session_index, + int distance) -> sessions_list::index_t { + assert(current_drag_operation && "drag_session must be called between " + "begin_dragging() and end_dragging() calls"); - auto new_indexes = current_drag_operation->record_drag(session.time_slots, distance); + if (session_index < 0 || + session_index > sessions().size() - 1 || + distance == 0) { + return session_index; + } - time_slots_changed(); + const auto &session = sessions()[session_index]; + if (session.activity == stg::strategy::no_activity) { + return session_index; + } - auto new_session_index = new_indexes.empty() - ? session_index - : sessions().session_index_for_time_slot_index(new_indexes.front()); + auto new_indexes = current_drag_operation->record_drag(session.time_slots, distance); - return new_session_index; -} + time_slots_changed(); -void stg::strategy::begin_dragging(session_index_t session_index) { - auto &session = sessions()[session_index]; - auto initial_indices = global_slot_indices_from_session(session); + auto new_session_index = new_indexes.empty() + ? session_index + : sessions().session_index_for_time_slot_index(new_indexes.front()); - current_drag_operation = std::make_shared(&_time_slots, initial_indices); -} + return new_session_index; + } -std::vector -stg::strategy::global_slot_indices_from_session(const session &session) const { - drag_operation::indices_vector initial_indices; - std::transform(session.time_slots.begin(), - session.time_slots.end(), - std::back_inserter(initial_indices), - [this](auto &slot) -> time_slots_state::index_t { - return *_time_slots.index_of(slot); - }); - - return initial_indices; -} + void stg::strategy::begin_dragging(session_index_t session_index) { + const auto &session = sessions()[session_index]; + auto initial_indices = global_slot_indices_from_session(session); -void stg::strategy::end_dragging() { - if (!current_drag_operation) { - return; + current_drag_operation = std::make_shared(&_time_slots, initial_indices); } - if (current_drag_operation->state_changed()) { - commit_to_history(); + auto strategy::global_slot_indices_from_session(const session &session) const + -> std::vector { + drag_operation::indices_vector initial_indices; + std::transform(session.time_slots.begin(), + session.time_slots.end(), + std::back_inserter(initial_indices), + [this](auto &slot) -> time_slots_state::index_t { + return *_time_slots.index_of(slot); + }); + + return initial_indices; } - current_drag_operation.reset(); -} + void strategy::end_dragging() { + if (!current_drag_operation) { + return; + } -void stg::strategy::cancel_dragging() { - _time_slots.reset_with(current_drag_operation->initial_state()); - current_drag_operation.reset(); + if (current_drag_operation->state_changed()) { + commit_to_history(); + } - time_slots_changed(); -} + current_drag_operation.reset(); + } -void stg::strategy::begin_resizing() { - current_resize_operation = std::make_shared(&_time_slots); -} + void strategy::cancel_dragging() { + _time_slots.reset_with(current_drag_operation->initial_state()); + current_drag_operation.reset(); -void stg::strategy::end_resizing() { - if (!current_resize_operation) { - return; + time_slots_changed(); } - if (current_resize_operation->state_changed()) { - commit_to_history(); + void strategy::begin_resizing() { + current_resize_operation = std::make_shared(&_time_slots); } - current_resize_operation.reset(); -} + void strategy::end_resizing() { + if (!current_resize_operation) { + return; + } + + if (current_resize_operation->state_changed()) { + commit_to_history(); + } -void stg::strategy::copy_session(stg::strategy::session_index_t session_index, - stg::strategy::time_slot_index_t begin_index) { - auto &session = sessions()[session_index]; - if (!session.activity) { - return; + current_resize_operation.reset(); } - auto copied_session_indices = std::vector(session.length()); - std::iota(copied_session_indices.begin(), - copied_session_indices.end(), - begin_index); + void strategy::copy_session(session_index_t session_index, + time_slot_index_t begin_index) { + const auto &session = sessions()[session_index]; + if (!session.activity) { + return; + } - _time_slots.set_activity_at_indices(session.activity, copied_session_indices); + auto copied_session_indices = std::vector(session.length()); + std::iota(copied_session_indices.begin(), + copied_session_indices.end(), + begin_index); - commit_to_history(); -} + _time_slots.set_activity_at_indices(session.activity, copied_session_indices); -stg::strategy::duration_t stg::strategy::duration() const { - if (time_slots().empty()) { - return duration_t(); + commit_to_history(); } - return time_slots().last().end_time() - time_slots().first().begin_time; -} - -std::string stg::strategy::to_json_string() const { - return json::serialize(*this); -} - -std::unique_ptr stg::strategy::from_json_string(const std::string &json_string) { - return json::parse(json_string); -} + auto strategy::duration() const -> duration_t { + if (time_slots().empty()) { + return duration_t(); + } -const stg::session *stg::strategy::get_current_session() const { - auto it = std::find_if(sessions().begin(), - sessions().end(), - [](const session &session) { - return session.is_current(); - }); - - if (it != sessions().end()) { - return &*it; + return time_slots().last().end_time() - time_slots().first().begin_time; } - return nullptr; -} + auto strategy::to_json_string() const -> std::string { + return json::serialize(*this); + } -const stg::session *stg::strategy::active_session() const { - auto current_session = this->get_current_session(); - if (!current_session || !current_session->activity) { - return nullptr; + auto strategy::from_json_string(const std::string &json_string) -> std::unique_ptr { + return json::parse(json_string); } - return current_session; -} + auto strategy::get_current_session() const -> const session * { + auto it = std::find_if(sessions().begin(), + sessions().end(), + [](const session &session) { + return session.is_current(); + }); -const stg::session *stg::strategy::upcoming_session() const { - auto current_session = this->get_current_session(); + if (it != sessions().end()) { + return &*it; + } - if (!current_session) { - // we're out of strategy's time bounds. return nullptr; } - const auto next_session = sessions().session_after(*current_session); - return next_session && next_session->activity - ? next_session - : nullptr; -} + auto strategy::active_session() const -> const session * { + const auto *current_session = this->get_current_session(); + if (!current_session || !current_session->activity) { + return nullptr; + } -void stg::strategy::reorder_activities_by_usage() { - std::map usage; + return current_session; + } - for (auto &session : sessions()) { - if (session.activity == no_activity) - continue; + auto strategy::upcoming_session() const -> const session * { + const auto *current_session = this->get_current_session(); - usage[session.activity] += session.duration(); - } + if (!current_session) { + // we're out of strategy's time bounds. + return nullptr; + } - std::vector> pairs; - for (const auto &elem : usage) { - pairs.emplace_back(elem); + const auto *next_session = sessions().session_after(*current_session); + return next_session && next_session->activity + ? next_session + : nullptr; } - std::sort(pairs.begin(), - pairs.end(), - [=](auto &a, auto &b) { - return a.second > b.second; - }); + void strategy::reorder_activities_by_usage() { + std::map usage; - std::vector> reordered; - for (auto &elem : pairs) { - if (!elem.first) - continue; + for (const auto &session : sessions()) { + if (session.activity == no_activity) + continue; - auto index = *activities().index_of(elem.first); - auto activity = _activities._data[index]; + usage[session.activity] += session.duration(); + } - reordered.push_back(activity); - } + std::vector> pairs; + for (const auto &elem : usage) { + pairs.emplace_back(elem); + } + + std::sort(pairs.begin(), + pairs.end(), + [=](auto &a, auto &b) { + return a.second > b.second; + }); - for (auto &activity : _activities) { - if (std::find_if(reordered.begin(), - reordered.end(), - [activity](auto a) { - return a == activity; - }) == reordered.end()) { + std::vector> reordered; + for (auto &elem : pairs) { + if (!elem.first) + continue; + + auto index = *activities().index_of(elem.first); + auto activity = _activities._data[index]; reordered.push_back(activity); - }; + } + + for (const auto &activity : _activities) { + if (std::find_if(reordered.begin(), + reordered.end(), + [activity](auto a) { + return a == activity; + }) == reordered.end()) { + + reordered.push_back(activity); + }; + } + + if (_activities.data() != reordered) { + _activities.reset_with(reordered); + _activities.on_change_event(); + + commit_to_history(); + } } - if (_activities.data() != reordered) { - _activities.reset_with(reordered); - _activities.on_change_event(); + auto strategy::is_dragging() -> bool { + return current_drag_operation != nullptr; + } - commit_to_history(); + auto strategy::is_resizing() -> bool { + return current_resize_operation != nullptr; } -} -bool stg::strategy::is_dragging() { - return current_drag_operation != nullptr; -} + auto strategy::from_file(const std::string &path) -> std::unique_ptr { + std::ifstream file(path); -bool stg::strategy::is_resizing() { - return current_resize_operation != nullptr; -} + if (file.is_open()) { + std::stringstream buffer; + buffer << file.rdbuf(); + auto json_string = buffer.str(); + + file.close(); -std::unique_ptr stg::strategy::from_file(const std::string &path) { - std::ifstream file(path); + return from_json_string(json_string); + } else { + throw file_read_exception(); + } + } - if (file.is_open()) { - std::stringstream buffer; - buffer << file.rdbuf(); - auto json_string = buffer.str(); + void strategy::write_to_file(const std::string &path) const { + auto file = std::ofstream(path); - file.close(); + if (file.is_open()) { + file << to_json_string(); + file.close(); + } else { + throw file_write_exception(); + } + } - return from_json_string(json_string); - } else { - throw file_read_exception(); + auto strategy::has_activities_undo() -> bool { + return history.has_prevoius_activities_state(); } -} -void stg::strategy::write_to_file(const std::string &path) const { - auto file = std::ofstream(path); + auto strategy::has_activities_redo() -> bool { + return history.has_next_activities_state(); + } - if (file.is_open()) { - file << to_json_string(); - file.close(); - } else { - throw file_write_exception(); + auto strategy::has_undo() -> bool { + return history.has_prevoius_state(); } -} -bool stg::strategy::has_activities_undo() { - return history.has_prevoius_activities_state(); -} + auto strategy::has_redo() -> bool { + return history.has_next_state(); + } -bool stg::strategy::has_activities_redo() { - return history.has_next_activities_state(); } - diff --git a/models/strategy.h b/models/strategy.h index 2488236..a90d456 100644 --- a/models/strategy.h +++ b/models/strategy.h @@ -40,77 +40,81 @@ namespace stg { #pragma mark - Constructors & Operators - explicit strategy(time_t begin_time_t = defaults::begin_time, - duration_t time_slot_duration_t = defaults::time_slot_duration, - size_t number_of_time_slots = defaults::number_of_time_slots); - + explicit strategy(time_t + begin_time_t = defaults::begin_time, + duration_t + time_slot_duration_t = defaults::time_slot_duration, + size_t + number_of_time_slots = defaults::number_of_time_slots + ); strategy(const time_slots_state::data_t &time_slots, const activity_list::data_t &activities); - strategy(const strategy &other); - strategy &operator=(const strategy &other); + auto operator=(const strategy &other) -> strategy &; #pragma mark - Import & Export - static std::unique_ptr from_json_string(const std::string &json_string); - std::string to_json_string() const; + static auto from_json_string(const std::string &json_string) -> std::unique_ptr; + auto to_json_string() const -> std::string; - static std::unique_ptr from_file(const std::string &path) noexcept(false); + static auto from_file(const std::string &path) noexcept(false) -> std::unique_ptr; void write_to_file(const std::string &path) const noexcept(false); #pragma mark - Collections - const activity_list &activities() const; - const sessions_list &sessions() const; - const time_slots_state &time_slots() const; + auto activities() const -> const activity_list &; + auto sessions() const -> const sessions_list &; + auto time_slots() const -> const time_slots_state &; #pragma mark - Time Grid Properties - time_t begin_time() const; + auto begin_time() const -> time_t; void set_begin_time(time_t begin_time); - duration_t time_slot_duration() const; + auto time_slot_duration() const -> duration_t; void set_time_slot_duration(duration_t time_slot_duration); - size_t number_of_time_slots() const; + auto number_of_time_slots() const -> size_t; void set_number_of_time_slots(size_t number_of_time_slots); - time_t end_time() const; - duration_t duration() const; + auto end_time() const -> time_t; + void set_end_time(time_t end_time); + + auto duration() const -> duration_t; -#pragma mark - Real-time Properties +#pragma mark - Real-Time Properties - const session *active_session() const; - const session *upcoming_session() const; + auto active_session() const -> const session *; + auto upcoming_session() const -> const session *; #pragma mark - Operations On Activities void add_activity(const activity &activity) noexcept(false); void silently_add_activity(const activity &activity) noexcept(false); - void delete_activity(activity_index activity_index); - void silently_delete_activity(activity_index activity_index); - void edit_activity(activity_index activity_index, const activity &new_activity) noexcept(false); - void silently_edit_activity(activity_index activity_index, const activity &new_activity) noexcept(false); - void drag_activity(activity_index from_index, activity_index to_index); - void silently_drag_activity(activity_index from_index, activity_index to_index); + void delete_activity(activity_index_t activity_index); + void silently_delete_activity(activity_index_t activity_index); + void edit_activity(activity_index_t activity_index, const activity &new_activity) noexcept(false); + void silently_edit_activity(activity_index_t activity_index, const activity &new_activity) noexcept(false); + void drag_activity(activity_index_t from_index, activity_index_t to_index); + void silently_drag_activity(activity_index_t from_index, activity_index_t to_index); void reorder_activities_by_usage(); #pragma mark - Operations On Slots - void place_activity(activity_index activity_index, const std::vector &time_slot_indices); + void place_activity(activity_index_t activity_index, const std::vector &time_slot_indices); void make_empty_at(const std::vector &time_slot_indices); void shift_below_time_slot(time_slot_index_t from_index, int length); - bool is_resizing(); + auto is_resizing() -> bool; void begin_resizing(); void fill_time_slots_shifting(time_slot_index_t from_index, time_slot_index_t till_index); void fill_time_slots(time_slot_index_t from_index, time_slot_index_t till_index); void end_resizing(); - bool is_dragging(); + auto is_dragging() -> bool; void begin_dragging(session_index_t session_index); - stg::sessions_list::index_t drag_session(session_index_t session_index, int distance); + auto drag_session(session_index_t session_index, int distance) -> sessions_list::index_t; void end_dragging(); void cancel_dragging(); @@ -121,8 +125,12 @@ namespace stg { void commit_to_history(); void undo(); void redo(); - bool has_activities_undo(); - bool has_activities_redo(); + + auto has_undo() -> bool; + auto has_redo() -> bool; + + auto has_activities_undo() -> bool; + auto has_activities_redo() -> bool; private: activity_list _activities; time_slots_state _time_slots; @@ -136,13 +144,12 @@ namespace stg { void setup_time_slots_callback(); // current session, may be empty - const session *get_current_session() const; + auto get_current_session() const -> const session *; - strategy_history::entry make_history_entry(); + auto make_history_entry() -> strategy_history::entry; void apply_history_entry(const std::optional &history_entry); - std::vector - global_slot_indices_from_session(const session &session) const; + auto global_slot_indices_from_session(const session &session) const -> std::vector; }; } diff --git a/models/tests/activity_tests.cpp b/models/tests/activity_tests.cpp index bd5177e..a542b24 100644 --- a/models/tests/activity_tests.cpp +++ b/models/tests/activity_tests.cpp @@ -40,13 +40,13 @@ TEST_CASE("stg::activity immutability", "[activity]") { const auto activity = stg::activity(intial_name); SECTION("copy changing name") { - REQUIRE(activity.copy_changing_name("Some 2") + REQUIRE(activity.with_name("Some 2") == stg::activity("Some 2")); REQUIRE(activity == stg::activity(intial_name)); } SECTION("copy changing color") { - REQUIRE(activity.copy_changing_color(RED_COLOR) + REQUIRE(activity.with_color(RED_COLOR) == stg::activity(intial_name, RED_COLOR)); REQUIRE(activity == stg::activity(intial_name)); } diff --git a/models/theme.cpp b/models/theme.cpp new file mode 100644 index 0000000..71ae77c --- /dev/null +++ b/models/theme.cpp @@ -0,0 +1,87 @@ +// +// Created by Dmitry Khrykin on 26/04/2020. +// + +#include "theme.h" + +namespace stg { + + auto theme::text_color() const -> color { + return get_text_color(); + } + + auto theme::base_color() const -> color { + return get_base_color(); + } + + auto theme::session_background_color(const session &session, bool is_selected) const -> color { + if (!session.activity) + return color::clear_color(); + + auto activity_color = session.activity->color(); + + if (activity_color.lightness() < 0.5 && + is_dark_mode()) { + activity_color.set_hsl(activity_color.hue(), activity_color.saturation(), 0.2); + } + + return is_selected + ? activity_color + : activity_color.with_alpha_component(0.15); + } + + auto theme::session_ruler_color(const session &session, bool is_selected) const -> color { + return is_selected + ? session.activity->desaturated_dark_color() + : session.activity->desaturated_light_color(); + } + + auto theme::session_duration_color(const session &session, bool is_selected) const -> color { + if (!session.activity) + return color::clear_color(); + + const auto activity_color = session.activity->color(); + auto default_duration_color = text_color() + .blended_with(activity_color + .with_hsl(activity_color.hue(), 0.2, 0.7) + .with_alpha_component(0.6)); + + auto selected_duration_color = base_color() + .blended_with(activity_color + + .with_alpha_component(0.2)); + + auto duration_color = is_selected + ? selected_duration_color + : default_duration_color; + + + if (session.activity->color().lightness() < 0.2 && is_selected) { + duration_color = color::white_color() + .blended_with(session.activity->color().with_alpha_component(0.5)); + } + + return duration_color; + } + + auto theme::session_title_color(const session &session, bool is_selected) const -> color { + auto activity_color = session.activity->color(); + auto lightened_activity_color = activity_color.with_hsl(activity_color.hue(), + activity_color.saturation(), + 1 - activity_color.lightness()); + + if (is_selected) { + return color::white_color(); + } else { + if (activity_color.lightness() < 0.5 && is_dark_mode()) + return lightened_activity_color; + + return session.activity->color(); + } + } + + auto theme::is_dark_mode() const -> bool { + return text_color().lightness() > 0.2; + } + +} diff --git a/models/theme.h b/models/theme.h new file mode 100644 index 0000000..38a15e3 --- /dev/null +++ b/models/theme.h @@ -0,0 +1,33 @@ +// +// Created by Dmitry Khrykin on 26/04/2020. +// + +#ifndef STRATEGR_THEME_H +#define STRATEGR_THEME_H + +#include +#include + +#include "color.h" +#include "session.h" +#include "activity.h" + +namespace stg { + struct theme { + std::function get_text_color = [] { + return color::black_color(); + }; + std::function get_base_color = [] { return color::white_color(); }; + + auto text_color() const -> color; + auto base_color() const -> color; + auto is_dark_mode() const -> bool; + + auto session_background_color(const session &session, bool is_selected) const -> color; + auto session_ruler_color(const session &session, bool is_selected) const -> color; + auto session_duration_color(const session &session, bool is_selected) const -> color; + auto session_title_color(const session &session, bool is_selected) const -> color; + }; +} + +#endif //STRATEGR_THEME_H diff --git a/models/time_utils.cpp b/models/time_utils.cpp index d623ed5..b23d602 100644 --- a/models/time_utils.cpp +++ b/models/time_utils.cpp @@ -6,88 +6,87 @@ #include "time_utils.h" namespace stg { -time_utils::minutes time_utils::current_minutes() { - return current_seconds() / 60; -} - -time_utils::seconds time_utils::current_seconds() { - auto duration = current_day_duration(); - return static_cast(std::chrono::duration_cast(duration).count()); -} + auto time_utils::current_minutes() -> time_utils::minutes { + return current_seconds() / 60; + } -time_utils::timestamp -time_utils::start_of_a_day_from_timestamp(time_utils::timestamp timestamp) { - auto day_components = *day_components_from_timestamp(×tamp); + auto time_utils::current_seconds() -> time_utils::seconds { + auto duration = current_day_duration(); + return static_cast(std::chrono::duration_cast(duration).count()); + } - day_components.tm_hour = 0; - day_components.tm_min = 0; - day_components.tm_sec = 0; + auto time_utils::start_of_a_day_from_timestamp(timestamp timestamp) -> time_utils::timestamp { + auto day_components = *day_components_from_timestamp(×tamp); - timestamp = timestamp_from_day_components(&day_components); + day_components.tm_hour = 0; + day_components.tm_min = 0; + day_components.tm_sec = 0; - return timestamp; -} + timestamp = timestamp_from_day_components(&day_components); -time_utils::duration time_utils::current_day_duration() { - using namespace std::chrono; + return timestamp; + } - auto clock_now = system_clock::now(); - auto current_timestamp = system_clock::to_time_t(clock_now); + auto time_utils::current_day_duration() -> duration { + using namespace std::chrono; - auto start_of_a_day_timestamp = start_of_a_day_from_timestamp(current_timestamp); - auto clock_start_of_today = system_clock::from_time_t(start_of_a_day_timestamp); + auto clock_now = system_clock::now(); + auto current_timestamp = system_clock::to_time_t(clock_now); - return clock_now - clock_start_of_today; -} + auto start_of_a_day_timestamp = start_of_a_day_from_timestamp(current_timestamp); + auto clock_start_of_today = system_clock::from_time_t(start_of_a_day_timestamp); -std::string time_utils::human_string_from_minutes(time_utils::minutes minutes) { - if (minutes < 1) { - return "Less than 1 min"; + return clock_now - clock_start_of_today; } - unsigned int hours = minutes / 60; - unsigned int mins_remainder = minutes - hours * 60; - - std::string result; + auto time_utils::human_string_from_minutes(minutes minutes) -> std::string { + if (minutes < 1) { + return "Less than 1 min"; + } - if (hours > 0) { - result += std::to_string(hours); - } + unsigned int hours = minutes / 60; + unsigned int mins_remainder = minutes - hours * 60; - if (hours > 0 && mins_remainder == 30) { - result += ".5 h"; - } else if (hours > 0) { - result += " h"; - } + std::string result; - if (mins_remainder != 0) { if (hours > 0) { - if (mins_remainder == 30) { - return result; + result += std::to_string(hours); + } + + if (hours > 0 && mins_remainder == 30) { + result += ".5 h"; + } else if (hours > 0) { + result += " h"; + } + + if (mins_remainder != 0) { + if (hours > 0) { + if (mins_remainder == 30) { + return result; + } + + result += " "; } - result += " "; + result += std::to_string(mins_remainder) + " min"; } - result += std::to_string(mins_remainder) + " min"; + return result; } - return result; -} - -std::string time_utils::string_from_seconds(time_utils::minutes total_seconds) { - auto hours = total_seconds / 3600; - auto minutes = (total_seconds - 3600 * hours) / 60; - auto seconds = total_seconds - 3600 * hours - 60 * minutes; + auto time_utils::string_from_seconds(minutes total_seconds) -> std::string { + auto hours = total_seconds / 3600; + auto minutes = (total_seconds - 3600 * hours) / 60; + auto seconds = total_seconds - 3600 * hours - 60 * minutes; - std::string result = std::to_string(hours) + " h"; + std::string result = std::to_string(hours) + " h"; - if (minutes) - result += " " + std::to_string(minutes) + " m"; + if (minutes) + result += " " + std::to_string(minutes) + " m"; - if (seconds) - result += " " + std::to_string(seconds) + "s"; + if (seconds) + result += " " + std::to_string(seconds) + "s"; - return result; -} + return result; + } }; diff --git a/models/time_utils.h b/models/time_utils.h index 5c67ce3..62de079 100644 --- a/models/time_utils.h +++ b/models/time_utils.h @@ -21,13 +21,13 @@ namespace stg { constexpr static auto day_components_from_timestamp = std::localtime; constexpr static auto timestamp_from_day_components = std::mktime; - timestamp start_of_a_day_from_timestamp(timestamp timestamp); - duration current_day_duration(); - minutes current_minutes(); - seconds current_seconds(); + auto start_of_a_day_from_timestamp(timestamp timestamp) -> time_utils::timestamp; + auto current_day_duration() -> duration; + auto current_minutes() -> minutes; + auto current_seconds() -> seconds; - std::string string_from_seconds(minutes total_seconds); - std::string human_string_from_minutes(minutes minutes); + auto string_from_seconds(minutes total_seconds) -> std::string; + auto human_string_from_minutes(minutes minutes) -> std::string; }; } diff --git a/models/timeslotsstate.cpp b/models/timeslotsstate.cpp index 85f4590..d906d45 100644 --- a/models/timeslotsstate.cpp +++ b/models/timeslotsstate.cpp @@ -12,12 +12,21 @@ stg::time_slots_state::time_t stg::time_slots_state::begin_time() const { } void stg::time_slots_state::set_begin_time(time_t begin_time) { + if (begin_time == _begin_time) + return; + _begin_time = begin_time; + + auto max_number_of_slots = 24 * 60 / slot_duration(); + while (number_of_slots() > max_number_of_slots) { + _data.pop_back(); + } + update_begin_times(); + on_change_event(); } - void stg::time_slots_state::update_begin_times() { for (auto slot_index = 0; slot_index < number_of_slots(); slot_index++) { _data[slot_index].begin_time @@ -30,6 +39,9 @@ stg::time_slots_state::duration_t stg::time_slots_state::slot_duration() const { } void stg::time_slots_state::set_slot_duration(duration_t slot_duration) { + if (slot_duration == _slot_duration) + return; + _slot_duration = slot_duration; for (auto slot_index = 0; slot_index < number_of_slots(); slot_index++) { @@ -74,6 +86,19 @@ stg::time_slots_state::time_slots_state(std::vector from_vector) { reset_times(); } +void stg::time_slots_state::set_end_time(time_t end_time) { + if (end_time <= begin_time()) { + end_time += 24 * 60; + } + + auto number_of_slots = (end_time - begin_time()) / slot_duration(); + set_number_of_slots(number_of_slots); +} + +auto stg::time_slots_state::end_time() const -> time_t { + return last().end_time(); +} + void stg::time_slots_state::reset_times() { auto first_slot = _data.front(); _begin_time = first_slot.begin_time; diff --git a/models/timeslotsstate.h b/models/timeslotsstate.h index ca22110..d469904 100644 --- a/models/timeslotsstate.h +++ b/models/timeslotsstate.h @@ -61,6 +61,9 @@ namespace stg { size_t number_of_slots() const; void set_number_of_slots(size_t new_number_of_slots); + auto end_time() const -> time_t; + void set_end_time(time_t end_time); + void set_activity_at_indices(activity *activity, const std::vector &indices); diff --git a/ui/activityeditormenu.cpp b/ui/activityeditormenu.cpp index cd944f2..cb0c721 100644 --- a/ui/activityeditormenu.cpp +++ b/ui/activityeditormenu.cpp @@ -53,11 +53,12 @@ void ActivityEditorMenu::setupColorDialog() { void ActivityEditorMenu::addCustomColorAction() { customColorAction = new QAction(tr("Custom Color")); customColorAction->setCheckable(true); + customColorAction->setChecked(!colorPicker->color()); connect(customColorAction, &QAction::triggered, [=]() { if (activity) { - colorDialog->setCurrentColor(ColorUtils::QColorFromStdString(activity->color())); + colorDialog->setCurrentColor(activity->color()); } else if (colorPicker->color()) { colorDialog->setCurrentColor(*colorPicker->color()); } diff --git a/ui/application.cpp b/ui/application.cpp index 29e8d0c..7d87ac1 100644 --- a/ui/application.cpp +++ b/ui/application.cpp @@ -6,6 +6,7 @@ #include #include "application.h" +#include "colorprovider.h" #ifdef Q_OS_WIN @@ -144,6 +145,20 @@ void Application::clearRecentFiles() { #endif } +auto Application::theme() -> const stg::theme & { + static auto theme = stg::theme(); + + theme.get_text_color = [] { + return ColorProvider::textColor(); + }; + + theme.get_base_color = [] { + return ColorProvider::baseColor(); + }; + + return theme; +} + #ifdef Q_OS_WIN void Application::checkForUpdates() { diff --git a/ui/application.h b/ui/application.h index 86f0894..58f2679 100644 --- a/ui/application.h +++ b/ui/application.h @@ -13,6 +13,7 @@ #include "aboutwindow.h" #include "mainwindow.h" +#include "models/theme.h" #ifdef Q_OS_MAC Q_FORWARD_DECLARE_OBJC_CLASS(CocoaDelegate); @@ -35,6 +36,7 @@ class Application : public QApplication { static void markFileClosed(const QString &filePath); static bool fileIsOpened(const QString &filePath); static void checkForUpdates(); + static auto theme() -> const stg::theme &; private: static void setupFonts(); diff --git a/ui/colorpicker.cpp b/ui/colorpicker.cpp index 1f63b2f..e27722c 100644 --- a/ui/colorpicker.cpp +++ b/ui/colorpicker.cpp @@ -9,10 +9,8 @@ ColorPicker::ColorPicker(QWidget *parent) : QWidget(parent) { auto *mainLayout = new QHBoxLayout(this); mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0, - 0, - 0, - 0); + mainLayout->setContentsMargins(0, 0, 0, 0); + for (const auto &color : colors) { auto *item = new ColorPickerItem(color, this); connect(item, @@ -34,12 +32,13 @@ void ColorPicker::setColor(const std::optional &color) { if (!color) return deselectAll(); - auto indexOfColor = std::distance(colors.begin(), std::find(colors.begin(), colors.end(), *color)); - if (indexOfColor < 0) + auto it = std::find(colors.begin(), colors.end(), *color); + if (it == colors.end()) return deselectAll(); _color = color; + auto indexOfColor = std::distance(colors.begin(), it); for (auto i = 0; i < layout()->count(); i++) { auto *item = qobject_cast(layout()->itemAt(i)->widget()); item->setIsSelected(indexOfColor == i); diff --git a/ui/colorprovider.cpp b/ui/colorprovider.cpp index 9fabdfd..0c596f7 100644 --- a/ui/colorprovider.cpp +++ b/ui/colorprovider.cpp @@ -31,13 +31,15 @@ QColor ColorProvider::selectionColor() { return selectionColor; } -//#ifndef Q_OS_MAC +#ifndef Q_OS_MAC QColor ColorProvider::controlColor() { - return highlightColor(); + auto color = highlightColor(); + color.setHsvF(color.hueF(), 1.0, color.valueF()); + return color; } -//#endif +#endif QColor ColorProvider::textColor() { return QApplication::palette().color(QPalette::Text); diff --git a/ui/colorprovider.mm b/ui/colorprovider.mm index ce0c2ca..2893d09 100644 --- a/ui/colorprovider.mm +++ b/ui/colorprovider.mm @@ -7,10 +7,16 @@ #include "colorprovider.h" -//QColor ColorProvider::controlColor() { -// NSColor *nsColor = [NSColor controlAccentColor]; -// return QColor(static_cast(nsColor.redComponent * 255), -// static_cast(nsColor.greenComponent * 255), -// static_cast(nsColor.blueComponent * 255), -// static_cast(nsColor.alphaComponent * 255)); -//} \ No newline at end of file +QColor ColorProvider::controlColor() { + NSColor *nsColor; + if (@available(macOS 10.14, *)) { + nsColor = [[NSColor controlAccentColor] colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + } else { + nsColor = [[NSColor linkColor] colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + } + + return QColor(static_cast(nsColor.redComponent * 255), + static_cast(nsColor.greenComponent * 255), + static_cast(nsColor.blueComponent * 255), + static_cast(nsColor.alphaComponent * 255)); +} \ No newline at end of file diff --git a/ui/selectionwidget.cpp b/ui/selectionwidget.cpp index f358e10..67b0404 100644 --- a/ui/selectionwidget.cpp +++ b/ui/selectionwidget.cpp @@ -39,9 +39,8 @@ void SelectionWidget::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); - auto penColor = selectionColor(); - penColor.setAlphaF(1.0); - penColor.setHsvF(penColor.hueF(), penColor.saturationF(), 1.0, 1.0); + auto penColor = controlColor(); + penColor.setAlphaF(0.5); painter.setPen(QPen(penColor, 1)); // painter.setPen(Qt::NoPen); @@ -53,7 +52,7 @@ void SelectionWidget::paintEvent(QPaintEvent *event) { ? clickedColor : selectionColor()); - for (auto &selectionItem : selection.grouped()) { + for (const auto &selectionItem : selection.grouped()) { drawSelectionForItem(selectionItem, painter); } } @@ -66,9 +65,9 @@ void SelectionWidget::drawSelectionForItem(const stg::grouped_selection_element const auto &lastTimeSlot = strategy.time_slots()[selectionItem.back()]; auto bottomMargin = lastTimeSlot.end_time() % 60 == 0 ? 1 : 0; - auto rect = QRect(contentsMargins().left(), + auto rect = QRect(contentsMargins().left() + 1, contentsMargins().top() + topPosition + 2, - width() - contentsMargins().right() - contentsMargins().left(), + width() - contentsMargins().right() - contentsMargins().left() - 1, widgetHeight - bottomMargin - 5); painter.drawRoundedRect(rect, 4, 4); diff --git a/ui/sessionwidget.cpp b/ui/sessionwidget.cpp index cd7dc99..e8b82ca 100644 --- a/ui/sessionwidget.cpp +++ b/ui/sessionwidget.cpp @@ -14,7 +14,9 @@ #include "strategy.h" #include "colorutils.h" #include "fontutils.h" +#include "application.h" #include "applicationsettings.h" +#include "theme.h" SessionWidget::SessionWidget(stg::session activitySession, QWidget *parent) @@ -52,23 +54,12 @@ void SessionWidget::paintEvent(QPaintEvent *) { } void SessionWidget::drawRulers(QPainter &painter) const { - auto blackColor = QColor(Qt::black); - auto desaturatedColor = sessionColor(); - desaturatedColor.setHslF(desaturatedColor.hueF(), 0.3, 0.75); - - auto rulerColor = isSelected() - ? ColorUtils::overlayWithAlpha(blackColor, 0.3, selectedBackgroundColor()) - : desaturatedColor; - - if (isSelected() && rulerColor.lightnessF() < 0.1) { - rulerColor = QColor(Qt::white); - } - - rulerColor.setAlphaF(0.2); + QColor rulerColor = Application::theme() + .session_ruler_color(activitySession, isSelected()); painter.setBrush(rulerColor); - for (auto &timeSlot : activitySession.time_slots) { + for (const auto &timeSlot : activitySession.time_slots) { auto timeSlotIndex = static_cast(&timeSlot - &activitySession.time_slots[0]); if (timeSlotIndex == activitySession.length() - 1) { @@ -90,44 +81,29 @@ void SessionWidget::drawBackground(QPainter &painter) const { return; } - auto color = isSelected() ? selectedBackgroundColor() : backgroundColor(); + QColor color = Application::theme() + .session_background_color(activitySession, isSelected()); painter.setRenderHint(QPainter::Antialiasing); painter.setBrush(color); auto lastTimeSlot = activitySession.time_slots.back(); auto bottomMargin = lastTimeSlot.end_time() % 60 == 0 || isBorderSelected ? 1 : 0; - auto selectionRect = QRect(0, - 2, - width(), - height() - 5 - bottomMargin); - - painter.drawRoundedRect(selectionRect, 4, 4); -} - -QColor SessionWidget::backgroundColor() const { - using namespace ColorUtils; - - if (!activitySession.activity) { - return QColor(255, 255, 255, 255); - } + auto backgroundRect = QRect(0, + 2, + width(), + height() - 5 - bottomMargin); - auto backgroundColor = sessionColor(); - backgroundColor.setAlphaF(0.15); - - return backgroundColor; + painter.drawRoundedRect(backgroundRect, 4, 4); } QColor SessionWidget::selectedBackgroundColor() const { - auto selectedBackgroundColor = backgroundColor(); - selectedBackgroundColor.setAlphaF(1); - - return selectedBackgroundColor; + return QColor(activitySession.activity->color()); } QColor SessionWidget::sessionColor() const { auto color = activitySession.activity - ? QColor(QString::fromStdString(activitySession.activity->color())) + ? QColor(activitySession.activity->color()) : QColor(); return color; } @@ -195,8 +171,7 @@ void SessionWidget::updateUI() { previousEndTime = activitySession.end_time(); } -void SessionWidget::setActivitySession( - const stg::session &newActivitySession) { +void SessionWidget::setActivitySession(const stg::session &newActivitySession) { activitySession = newActivitySession; updateUI(); } @@ -210,40 +185,23 @@ int SessionWidget::expectedHeight() { } void SessionWidget::drawLabel(QPainter &painter) const { + using namespace ApplicationSettings; + auto font = QFont(); font.setBold(true); - font.setPixelSize(ApplicationSettings::sessionFontSize); + font.setPixelSize(sessionFontSize); painter.setFont(font); - auto textRect = QRect(ApplicationSettings::defaultPadding, - ApplicationSettings::defaultPadding, - width() - 2 * ApplicationSettings::defaultPadding, - height() - 2 * ApplicationSettings::defaultPadding); - - auto sessionColorDesaturated - = ColorUtils::overlayWithAlpha(textColor(), 0.5, selectedBackgroundColor()); - sessionColorDesaturated.setHsvF(sessionColorDesaturated.hueF(), 0.2, 0.5); - - auto selectedDurationColor - = ColorUtils::overlayWithAlpha(baseColor(), 0.7, selectedBackgroundColor()); - - auto durationColor = isSelected() - ? selectedDurationColor - : sessionColorDesaturated; + auto textRect = QRect(defaultPadding, + defaultPadding, + width() - 2 * defaultPadding, + height() - 2 * defaultPadding); - auto titleColor = isSelected() ? Qt::white : sessionColor(); - - if (sessionColor() == QColor(Qt::black)) { - titleColor = textColor(); - } - - if (sessionColor().lightnessF() < 0.2) { - if (isSelected()) { - titleColor = Qt::white; - durationColor = ColorUtils::overlayWithAlpha(Qt::white, 0.5, sessionColor()); - } - } + auto durationColor = Application::theme() + .session_duration_color(activitySession, isSelected()); + auto titleColor = Application::theme() + .session_title_color(activitySession, isSelected()); FontUtils::drawSessionTitle(activitySession, painter, diff --git a/ui/sessionwidget.h b/ui/sessionwidget.h index 02f05a9..b500c41 100644 --- a/ui/sessionwidget.h +++ b/ui/sessionwidget.h @@ -47,7 +47,6 @@ Q_OBJECT void updateUI(); int expectedHeight(); - QColor backgroundColor() const; QColor selectedBackgroundColor() const; QColor sessionColor() const; diff --git a/ui/slotboardcircleswidget.cpp b/ui/slotboardcircleswidget.cpp index 8dbf1c8..82a3949 100644 --- a/ui/slotboardcircleswidget.cpp +++ b/ui/slotboardcircleswidget.cpp @@ -32,21 +32,21 @@ void SlotBoardCirclesWidget::paintEvent(QPaintEvent *event) { auto slotsRect = getSlotsRect(); - auto radius = 6; + auto diameter = 6; auto firstSlotTopOffset = slotsRect.top() + 2; auto topOffset = firstSlotTopOffset + (_slotBeforeResizeBoundaryIndex + 1) * getSlotHeight() - - 1 - radius / 2; + - 1 - diameter / 2; - auto circleLeftRect = QRect(slotsRect.left() + defaultPadding / 2 - 1, + auto circleLeftRect = QRect(slotsRect.left() - defaultPadding / 2 - 1, topOffset, - radius, - radius); - auto circleRightRect = QRect(slotsRect.right() - radius - defaultPadding + defaultPadding / 2 + 2, + diameter, + diameter); + auto circleRightRect = QRect(slotsRect.right() - diameter - defaultPadding + defaultPadding / 2 + 2, topOffset, - radius, - radius); + diameter, + diameter); painter.drawEllipse(circleLeftRect); painter.drawEllipse(circleRightRect); diff --git a/ui/slotboardwidget.cpp b/ui/slotboardwidget.cpp index 269b5bc..546cbce 100644 --- a/ui/slotboardwidget.cpp +++ b/ui/slotboardwidget.cpp @@ -23,6 +23,8 @@ SlotBoardWidget::SlotBoardWidget(stg::strategy &strategy, QWidget *parent) auto *mainLayout = new QHBoxLayout(); setLayout(mainLayout); + setMouseTracking(true); + layout()->setSpacing(0); layout()->setContentsMargins(0, 0, 0, 0); @@ -43,19 +45,18 @@ void SlotBoardWidget::setupCurrentTimeTimer() { } void SlotBoardWidget::layoutChildWidgets(QHBoxLayout *mainLayout) { - slotsWidget = new SlotsWidget(strategy); - connect(slotsWidget, + _slotsWidget = new SlotsWidget(strategy, this); + connect(_slotsWidget, &SlotsWidget::sessionsChanged, this, &SlotBoardWidget::handleTimeSlotsChange); - - slotRuler = new SlotRuler(makeLabelsForStrategy(), - slotsWidget->slotHeight()); + slotRuler = new SlotRuler(makeLabels(), + _slotsWidget->slotHeight(), this); _slotsLayout = new QVBoxLayout(); _slotsLayout->setSpacing(0); - _slotsLayout->addWidget(slotsWidget); + _slotsLayout->addWidget(_slotsWidget); _slotsLayout->addStretch(); updateSlotsLayout(); @@ -64,16 +65,26 @@ void SlotBoardWidget::layoutChildWidgets(QHBoxLayout *mainLayout) { mainLayout->addLayout(_slotsLayout); circlesWidget = new SlotBoardCirclesWidget( - std::bind(&SlotsWidget::slotHeight, slotsWidget), - std::bind(&SlotsWidget::geometry, slotsWidget), + std::bind(&SlotsWidget::slotHeight, _slotsWidget), + std::bind(&SlotsWidget::geometry, _slotsWidget), this ); - connect(slotsWidget, + connect(_slotsWidget, &SlotsWidget::resizeBoundaryChanged, circlesWidget, &SlotBoardCirclesWidget::updateResizeBoundary); + connect(_slotsWidget, + &SlotsWidget::resizeBoundaryChanged, + slotRuler, + &SlotRuler::updateList); + + connect(_slotsWidget, + &SlotsWidget::drawDraggedSession, + this, + &SlotBoardWidget::drawDraggedSession); + circlesWidget->setGeometry(geometry()); currentTimeMarkerWidget = new CurrentTimeMarkerWidget(this); @@ -82,24 +93,25 @@ void SlotBoardWidget::layoutChildWidgets(QHBoxLayout *mainLayout) { } void SlotBoardWidget::updateSlotsLayout() const { - _slotsLayout->setContentsMargins(0, slotsWidget->slotHeight() / 2, 0, 0); + _slotsLayout->setContentsMargins(0, _slotsWidget->slotHeight() / 2, 0, 0); } void SlotBoardWidget::reloadStrategy() { - slotsWidget->reloadStrategy(); + _slotsWidget->reloadStrategy(); updateUI(); } void SlotBoardWidget::updateUI() { - slotRuler->setLabels(makeLabelsForStrategy()); + slotRuler->setLabels(makeLabels()); updateCurrentTimeMarker(); } -QVector SlotBoardWidget::makeLabelsForStrategy() { +QVector SlotBoardWidget::makeLabels() { QVector labels; for (auto &time : strategy.time_slots().times()) { + auto minutes = time % 60; auto labelText = QStringForMinutes(time); auto label = TimeLabel{labelText, time}; @@ -110,14 +122,14 @@ QVector SlotBoardWidget::makeLabelsForStrategy() { } void SlotBoardWidget::clearSelection() { - slotsWidget->deselectAllSlots(); + _slotsWidget->deselectAllSlots(); } void SlotBoardWidget::updateCurrentTimeMarker() { auto currentTimeMarker = stg::current_time_marker(strategy); - auto rect = currentTimeMarker.rect_in_parent(slotsWidget->geometry(), - CurrentTimeMarkerWidget::markerSize); + auto parentRect = _slotsWidget->geometry(); + auto rect = currentTimeMarker.rect_in_parent(parentRect, CurrentTimeMarkerWidget::markerSize); currentTimeMarkerWidget->setGeometry(rect); @@ -133,8 +145,8 @@ void SlotBoardWidget::updateCurrentTimeMarker() { void SlotBoardWidget::focusOnCurrentTime() { auto topOffset = stg::current_time_marker(strategy) - .scroll_offset_in_parent(slotsWidget->geometry(), - parentWidget()->contentsRect().height()); + .scroll_offset(_slotsWidget->geometry(), + parentWidget()->contentsRect().height()); auto scrollBar = parentScrollArea()->verticalScrollBar(); @@ -153,7 +165,7 @@ QScrollArea *SlotBoardWidget::parentScrollArea() { void SlotBoardWidget::handleTimeSlotsChange() { updateCurrentTimeMarker(); - slotRuler->setLabels(makeLabelsForStrategy()); + slotRuler->setLabels(makeLabels()); emit timeSlotsChange(); } @@ -182,3 +194,105 @@ QVBoxLayout *SlotBoardWidget::slotsLayout() const { return _slotsLayout; } +SlotsWidget *SlotBoardWidget::slotsWidget() const { + return _slotsWidget; +} + +void SlotBoardWidget::drawDraggedSession(int sessionIndex, int firstSlotIndex) { + auto getRect = [=]() -> QRect { + const auto &session = strategy.sessions()[sessionIndex]; + + auto lastSlotIndex = firstSlotIndex + session.length(); + const auto &lastSlot = strategy.time_slots()[lastSlotIndex]; + auto bottomMargin = lastSlot.end_time() % 60 == 0 ? 2 : 4; + auto horizontalMargin = 8; + auto rect = QRect(slotRuler->width(), + firstSlotIndex * slotsWidget()->slotHeight() + slotsWidget()->geometry().top() + 2, + slotsWidget()->width() - horizontalMargin, + session.length() * slotsWidget()->slotHeight() - bottomMargin); + + return rect; + }; + + auto makeBigRect = [=](const QRect &smallRect) -> QRect { + auto bigRect = smallRect; + bigRect.setX(smallRect.x() - 2); + bigRect.setWidth(smallRect.width() + 4); + return bigRect; + }; + + auto makeSmallRect = [=](const QRect &bigRect) -> QRect { + auto smallRect = bigRect; + smallRect.setX(bigRect.x() + 2); + smallRect.setWidth(bigRect.width() - 4); + return smallRect; + }; + + bool initial = false; + + if (!draggedSessionWidget && sessionIndex >= 0) { + initial = true; + + draggedSessionWidget = new SessionWidget(strategy.sessions()[sessionIndex], this); + draggedSessionWidget->setDrawsBorders(false); + draggedSessionWidget->setIsSelected(true); + + auto shadowColor = textColor(); + shadowColor.setAlphaF(0.2); + + auto *effect = new QGraphicsDropShadowEffect; + effect->setBlurRadius(25); + effect->setXOffset(0); + effect->setYOffset(0); + effect->setColor(shadowColor); + + draggedSessionWidget->setGraphicsEffect(effect); + + draggedSessionWidget->setVisible(true); + + draggedSessionAnimation = new QPropertyAnimation(draggedSessionWidget, "geometry"); + draggedSessionAnimation->setDuration(50); + + auto smallRect = getRect(); + auto bigRect = makeBigRect(smallRect); + + draggedSessionWidget->setGeometry(smallRect); + draggedSessionAnimation->setStartValue(smallRect); + draggedSessionAnimation->setEndValue(bigRect); + + draggedSessionAnimation->start(); + } + + if (draggedSessionWidget && sessionIndex < 0) { + if (draggedSessionAnimation) + draggedSessionAnimation->deleteLater(); + + draggedSessionAnimation = new QPropertyAnimation(draggedSessionWidget, "geometry"); + draggedSessionAnimation->setDuration(50); + auto draggedSessionWidgetLocal = draggedSessionWidget; + draggedSessionWidget = nullptr; + + connect(draggedSessionAnimation, &QPropertyAnimation::finished, [this, draggedSessionWidgetLocal]() { + draggedSessionAnimation->deleteLater(); + draggedSessionWidgetLocal->deleteLater(); + + draggedSessionAnimation = nullptr; + }); + + auto smallRect = makeSmallRect(draggedSessionWidgetLocal->geometry()); + + draggedSessionAnimation->setStartValue(draggedSessionWidgetLocal->geometry()); + draggedSessionAnimation->setEndValue(smallRect); + + draggedSessionAnimation->start(); + } + + if (draggedSessionWidget && !initial) { + auto smallRect = getRect(); + auto bigRect = makeBigRect(smallRect); + + draggedSessionAnimation->stop(); + draggedSessionWidget->setGeometry(bigRect); + } +} + diff --git a/ui/slotboardwidget.h b/ui/slotboardwidget.h index 802b87c..886e7d3 100644 --- a/ui/slotboardwidget.h +++ b/ui/slotboardwidget.h @@ -3,6 +3,7 @@ #include #include +#include #include "strategy.h" #include "currenttimemarkerwidget.h" @@ -28,25 +29,28 @@ Q_OBJECT void clearSelection(); void focusOnCurrentTime(); + QVBoxLayout *slotsLayout() const; + SlotsWidget *slotsWidget() const; + signals: void timerTick(); void timeSlotsChange(); private: stg::strategy &strategy; - SlotsWidget *slotsWidget = nullptr; + SlotsWidget *_slotsWidget = nullptr; SlotBoardCirclesWidget *circlesWidget = nullptr; + SessionWidget *draggedSessionWidget = nullptr; SlotRuler *slotRuler = nullptr; QVBoxLayout *_slotsLayout = nullptr; + QPropertyAnimation *draggedSessionAnimation = nullptr; QTimer *currentTimeTimer = nullptr; CurrentTimeMarkerWidget *currentTimeMarkerWidget = nullptr; - int slotBeforeResizeBoundaryIndex = -2; - - QVector makeLabelsForStrategy(); + QVector makeLabels(); void handleTimeSlotsChange(); void updateUI(); @@ -56,11 +60,12 @@ Q_OBJECT QScrollArea *parentScrollArea(); void setupCurrentTimeTimer(); void timerCallback(); + void updateSlotsLayout() const; + void drawDraggedSession(int, int); void paintEvent(QPaintEvent *event) override; void resizeEvent(QResizeEvent *event) override; - void updateSlotsLayout() const; }; #endif // SLOTBOARD_H diff --git a/ui/slotruler.cpp b/ui/slotruler.cpp index cc1262c..05ad7e5 100644 --- a/ui/slotruler.cpp +++ b/ui/slotruler.cpp @@ -1,10 +1,13 @@ -#include "slotruler.h" #include #include #include #include + #include "applicationsettings.h" #include "utils.h" +#include "slotruler.h" +#include "slotboardwidget.h" +#include "slotswidget.h" SlotRuler::SlotRuler(const QVector &labels, int cellHeight, @@ -13,15 +16,17 @@ SlotRuler::SlotRuler(const QVector &labels, _cellHeight(cellHeight), QWidget(parent) { auto *layout = new QVBoxLayout(); - layout->setContentsMargins(ApplicationSettings::defaultPadding, 0, 0, 0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); setLayout(layout); - auto fontWidth = calculateLabelWidth() + ApplicationSettings::defaultPadding; + auto fontWidth = calculateLabelWidth() + 2 * ApplicationSettings::defaultPadding; auto width = std::max(fontWidth, 40); setFixedWidth(width); updateList(); + + selection().add_on_change_callback(this, &SlotRuler::updateList); } int SlotRuler::calculateLabelWidth() const { @@ -45,10 +50,38 @@ void SlotRuler::setLabels(const QVector &labels) { } void SlotRuler::paintEvent(QPaintEvent *) { - QStyleOption opt; - opt.init(this); QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + + auto handleBorderColor = highlightColor(); + handleBorderColor.setAlphaF(0.25); + + auto handleBgColor = highlightColor(); + handleBgColor.setAlphaF(0.1); + + p.setBrush(handleBgColor); + p.setPen(handleBorderColor); + +// for (auto &selectionSegment : selection().grouped()) { +// auto firstIndex = selectionSegment.front(); +// auto lastIndex = selectionSegment.back() + 1; +// +// auto handleWidth = calculateLabelWidth() + 4; +// auto handleHeight = bigFontHeight * 1.5; +// +// auto rect1 = QRect((width() - handleWidth) / 2, +// firstIndex * cellHeight() + (cellHeight() - handleHeight) / 2, +// handleWidth, +// handleHeight); +// +// auto rect2 = QRect((width() - handleWidth) / 2, +// lastIndex * cellHeight() + (cellHeight() - handleHeight) / 2, +// handleWidth, +// handleHeight); +// +// +// p.drawRoundedRect(rect1, 2, 2); +// p.drawRoundedRect(rect2, 2, 2); +// } } int SlotRuler::cellHeight() const { return _cellHeight; } @@ -77,6 +110,12 @@ SlotRuler::ColorGetter SlotRuler::labelColorGetterAtIndex(int index) { auto colorGetter = this->isIntegerHourAtIndex(index) ? &SlotRuler::secondaryTextColor : &SlotRuler::tertiaryTextColor; + + if (selection().is_boundary(index) || + slotsWidget()->slotBeforeBoundaryIndex() == index - 1) + colorGetter = &SlotRuler::controlColor; + + return colorGetter; } @@ -99,3 +138,12 @@ bool SlotRuler::isIntegerHourAtIndex(int index) const { return isIntegerHour; } +const stg::selection &SlotRuler::selection() const { + return slotsWidget()->selection(); +} + +SlotsWidget *SlotRuler::slotsWidget() const { + auto slotBoardWidget = qobject_cast(parentWidget()); + return slotBoardWidget->slotsWidget(); +} + diff --git a/ui/slotruler.h b/ui/slotruler.h index e7bf299..1471068 100644 --- a/ui/slotruler.h +++ b/ui/slotruler.h @@ -9,7 +9,9 @@ #include "timelabel.h" #include "coloredlabel.h" #include "colorprovider.h" +#include "selection.h" +class SlotsWidget; class SlotRuler : public QWidget, public ReactiveList, public ColorProvider { @@ -22,8 +24,15 @@ Q_OBJECT const QVector &labels() const; void setLabels(const QVector &labels); + const stg::selection &selection() const; + int cellHeight() const; void setCellHeight(int cellHeight); + + void updateList() { + ReactiveList::updateList(); + } + private: using ColorGetter = QColor (*)(); QVector _labels; @@ -34,6 +43,8 @@ Q_OBJECT ColorGetter labelColorGetterAtIndex(int index); + SlotsWidget *slotsWidget() const; + // ReactiveList int numberOfItems() override; QVBoxLayout *listLayout() override; diff --git a/ui/slotsmousehandler.cpp b/ui/slotsmousehandler.cpp index 7d2b9ec..74eb1dc 100644 --- a/ui/slotsmousehandler.cpp +++ b/ui/slotsmousehandler.cpp @@ -31,98 +31,7 @@ SlotsMouseHandler::SlotsMouseHandler(SlotsWidget *slotsWidget) handler.on_context_menu_event = std::bind(&SlotsMouseHandler::showContextMenu, this, _1, _2, _3); handler.on_open_activities = std::bind(&SlotsWidget::openActivitiesWindow, slotsWidget); handler.on_draw_dragged_session = [this](int sessionIndex, int firstSlotIndex) { - auto getRect = [=]() -> QRect { - const auto &session = strategy().sessions()[sessionIndex]; - - auto lastSlotIndex = firstSlotIndex + session.length(); - auto &lastSlot = strategy().time_slots()[lastSlotIndex]; - auto bottomMargin = lastSlot.end_time() % 60 == 0 ? 2 : 4; - auto horizontalMargin = 8; - auto rect = QRect(horizontalMargin, - firstSlotIndex * slotHeight() + 2, - width() - 2 * horizontalMargin, - session.length() * slotHeight() - bottomMargin); - - return rect; - }; - - auto makeBigRect = [=](const QRect &smallRect) -> QRect { - auto bigRect = smallRect; - bigRect.setX(smallRect.x() - 2); - bigRect.setWidth(smallRect.width() + 4); - return bigRect; - }; - - auto makeSmallRect = [=](const QRect &bigRect) -> QRect { - auto smallRect = bigRect; - smallRect.setX(bigRect.x() + 2); - smallRect.setWidth(bigRect.width() - 4); - return smallRect; - }; - - bool initial = false; - - if (!draggedSessionWidget && sessionIndex >= 0) { - initial = true; - - draggedSessionWidget = new SessionWidget(strategy().sessions()[sessionIndex], this); - draggedSessionWidget->setDrawsBorders(false); - draggedSessionWidget->setIsSelected(true); - - auto *effect = new QGraphicsDropShadowEffect; - effect->setBlurRadius(20); - effect->setXOffset(0); - effect->setYOffset(0); - effect->setColor(QColor(0xCCCCCC)); - - draggedSessionWidget->setGraphicsEffect(effect); - - draggedSessionWidget->setVisible(true); - - draggedSessionAnimation = new QPropertyAnimation(draggedSessionWidget, "geometry"); - draggedSessionAnimation->setDuration(50); - - auto smallRect = getRect(); - auto bigRect = makeBigRect(smallRect); - - draggedSessionWidget->setGeometry(smallRect); - draggedSessionAnimation->setStartValue(smallRect); - draggedSessionAnimation->setEndValue(bigRect); - - draggedSessionAnimation->start(); - } - - if (draggedSessionWidget && sessionIndex < 0) { - if (draggedSessionAnimation) - draggedSessionAnimation->deleteLater(); - - draggedSessionAnimation = new QPropertyAnimation(draggedSessionWidget, "geometry"); - draggedSessionAnimation->setDuration(50); - auto draggedSessionWidgetLocal = draggedSessionWidget; - draggedSessionWidget = nullptr; - - connect(draggedSessionAnimation, &QPropertyAnimation::finished, [this, draggedSessionWidgetLocal]() { - draggedSessionAnimation->deleteLater(); - draggedSessionWidgetLocal->deleteLater(); - - draggedSessionAnimation = nullptr; - }); - - auto smallRect = makeSmallRect(draggedSessionWidgetLocal->geometry()); - - draggedSessionAnimation->setStartValue(draggedSessionWidgetLocal->geometry()); - draggedSessionAnimation->setEndValue(smallRect); - - draggedSessionAnimation->start(); - } - - if (draggedSessionWidget && !initial) { - auto smallRect = getRect(); - auto bigRect = makeBigRect(smallRect); - - draggedSessionAnimation->stop(); - draggedSessionWidget->setGeometry(bigRect); - } + emit drawDraggedSession(sessionIndex, firstSlotIndex); }; } @@ -135,17 +44,15 @@ stg::selection &SlotsMouseHandler::selection() { } SlotBoardWidget *SlotsMouseHandler::slotBoard() { - return qobject_cast - (slotsWidget->parent()); + return qobject_cast(slotsWidget->parent()); } QScrollArea *SlotsMouseHandler::slotBoardScrollArea() { - return qobject_cast - (slotsWidget - ->parent() // layout - ->parent() // slotboard - ->parent() // scroll area - ->parent()) // layout + return qobject_cast(slotsWidget + ->parent() // layout + ->parent() // slotboard + ->parent() // scroll area + ->parent()) // layout ->slotBoardScrollArea(); } diff --git a/ui/slotsmousehandler.h b/ui/slotsmousehandler.h index 123e6d7..3b7adc1 100644 --- a/ui/slotsmousehandler.h +++ b/ui/slotsmousehandler.h @@ -27,6 +27,8 @@ Q_OBJECT signals: void resizeBoundaryChanged(int sessionBeforeBoundaryIndex, int slotBeforeBoundaryIndex); + void drawDraggedSession(int sessionIndex, + int firstSlotIndex); private: SlotsWidget *slotsWidget; @@ -43,8 +45,6 @@ Q_OBJECT QTimer *autoscrollAnimation = nullptr; int draggedSessionIndex = -1; stg::rect draggedSessionRect = stg::rect::zero; - SessionWidget *draggedSessionWidget = nullptr; - QPropertyAnimation *draggedSessionAnimation = nullptr; SessionWidget *sessionWidgetAtIndex(int sessionIndex); SlotBoardWidget *slotBoard(); diff --git a/ui/slotswidget.cpp b/ui/slotswidget.cpp index 913b548..1af372a 100644 --- a/ui/slotswidget.cpp +++ b/ui/slotswidget.cpp @@ -45,6 +45,11 @@ void SlotsWidget::layoutChildWidgets() { this, &SlotsWidget::updateResizeBoundary); + connect(mouseHandler, + &SlotsMouseHandler::drawDraggedSession, + this, + &SlotsWidget::drawDraggedSession); + layout()->addWidget(slotsWidget); layout()->addWidget(selectionWidget); layout()->addWidget(mouseHandler); @@ -189,32 +194,36 @@ void SlotsWidget::paintEvent(QPaintEvent *event) { painter.setPen(Qt::NoPen); - auto borderColor = slotBeforeBoundaryIndex == -1 - ? highlightColor() + auto borderColor = _slotBeforeBoundaryIndex == -1 + ? controlColor() : ColorProvider::borderColor(); painter.setBrush(borderColor); auto thickness = strategy.begin_time() % 60 == 0 ? 2 : 1; - auto borderRect = QRect(8, 0, width() - 8 * 2, thickness); + auto borderRect = QRect(0, 0, width() - ApplicationSettings::defaultPadding, thickness); painter.drawRect(borderRect); } void SlotsWidget::updateContentsMargins() { auto thickness = strategy.begin_time() % 60 == 0 ? 2 : 1; - slotsLayout->setContentsMargins(8, thickness, 8, 0); - selectionWidget->setContentsMargins(8, thickness, 8, 0); + slotsLayout->setContentsMargins(0, thickness, ApplicationSettings::defaultPadding, 0); + selectionWidget->setContentsMargins(0, thickness, ApplicationSettings::defaultPadding, 0); } void SlotsWidget::updateResizeBoundary(int sessionBeforeBoundaryIndex, int slotBeforeBoundaryIndex) { - this->slotBeforeBoundaryIndex = slotBeforeBoundaryIndex; + this->_slotBeforeBoundaryIndex = slotBeforeBoundaryIndex; update(); emit resizeBoundaryChanged(sessionBeforeBoundaryIndex, slotBeforeBoundaryIndex); } +int SlotsWidget::slotBeforeBoundaryIndex() const { + return _slotBeforeBoundaryIndex; +} + diff --git a/ui/slotswidget.h b/ui/slotswidget.h index 4048425..4cff4be 100644 --- a/ui/slotswidget.h +++ b/ui/slotswidget.h @@ -27,15 +27,19 @@ Q_OBJECT explicit SlotsWidget(stg::strategy &strategy, QWidget *parent = nullptr); - void reloadStrategy(); int slotHeight() const; + void reloadStrategy(); void deselectAllSlots(); + int slotBeforeBoundaryIndex() const; + const stg::selection &selection(); signals: void sessionsChanged(); void resizeBoundaryChanged(int, int); + void drawDraggedSession(int, int); + private: friend SlotsMouseHandler; stg::strategy &strategy; @@ -46,7 +50,7 @@ Q_OBJECT SlotsMouseHandler *mouseHandler = nullptr; int _slotHeight = ApplicationSettings::defaultSlotHeight; - int slotBeforeBoundaryIndex = -2; + int _slotBeforeBoundaryIndex = -2; QAction *setActivityAction = nullptr; QAction *deleteActivityAction = nullptr; @@ -65,7 +69,8 @@ Q_OBJECT void updateUI(); void updateContentsMargins(); - void updateResizeBoundary(int sessionBeforeBoundaryIndex, int slotBeforeBoundaryIndex); + void updateResizeBoundary(int sessionBeforeBoundaryIndex, + int slotBeforeBoundaryIndex); void onSelectionChange(); diff --git a/ui/strategysettingswidget.cpp b/ui/strategysettingswidget.cpp index dd39ef5..5cb0ac2 100644 --- a/ui/strategysettingswidget.cpp +++ b/ui/strategysettingswidget.cpp @@ -76,7 +76,7 @@ void StrategySettingsWidget::createSlotDurationForm() { slotDurationEdit->setAlignment(Qt::AlignRight); slotDurationEdit->setStyleSheet("color: #888;"); - auto slotDurationEditDecorator = new SpinBoxDecorator(slotDurationEdit, this); + auto *slotDurationEditDecorator = new SpinBoxDecorator(slotDurationEdit, this); slotDurationEditDecorator->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -215,7 +215,6 @@ void StrategySettingsWidget::save() { auto slotDuration = slotDurationEdit->value(); auto beginTime = minutesFromQTime(beginTimeEdit->time()); auto endTime = minutesFromQTime(endTimeEdit->time()); - auto numberOfSlots = (endTime - beginTime) / slotDuration; if (slotDuration != strategy.time_slot_duration()) { strategy.set_time_slot_duration(slotDuration); @@ -225,29 +224,12 @@ void StrategySettingsWidget::save() { strategy.set_begin_time(beginTime); } - if (numberOfSlots != strategy.number_of_time_slots()) { - strategy.set_number_of_time_slots(numberOfSlots); + if (endTime != strategy.end_time()) { + strategy.set_end_time(endTime); } } void StrategySettingsWidget::endTimeChanged(const QTime &time) { - auto slotDuration = slotDurationEdit->value(); - auto mins = minutesFromQTime(time); - if (mins <= minutesFromQTime(beginTimeEdit->time())) { - endTimeEdit->setTime(beginTimeEdit->time().addSecs(slotDuration * 60)); - return; - } - - auto remainder = mins % slotDuration; - if (remainder != 0) { - mins = remainder < slotDuration / 2 ? mins - remainder - : mins - remainder + slotDuration; - } - - if (mins != minutesFromQTime(time)) { - endTimeEdit->setTime(QTimeFromMinutes(mins)); - } - save(); }