From 0e5e49840645515c1f18d42b75798a8221d952d6 Mon Sep 17 00:00:00 2001 From: Matthias Meulien Date: Sat, 21 Oct 2023 17:03:44 +0200 Subject: [PATCH] Add labels to identify hourly forecast dates Refs: #62 --- src/hourlyforecastbox.cc | 86 ++++++++++++++++++++++----- src/hourlyforecastbox.h | 9 ++- src/keys.h | 2 +- src/util.cc | 124 +++++++++++++++++++++++++++++---------- src/util.h | 11 ++++ 5 files changed, 184 insertions(+), 48 deletions(-) diff --git a/src/hourlyforecastbox.cc b/src/hourlyforecastbox.cc index 2ccbc0d..0c5c62c 100644 --- a/src/hourlyforecastbox.cc +++ b/src/hourlyforecastbox.cc @@ -18,20 +18,19 @@ HourlyForecastBox::HourlyForecastBox(int pos_x, int pos_y, int width, std::shared_ptr fonts) : Widget{pos_x, pos_y, width, height}, model{model}, icons{icons}, fonts{fonts} { - this->visible_bars = 8; - - this->bar_width = static_cast(std::ceil(width / this->visible_bars)); + auto normal_font = this->fonts->get_normal_font(); + auto small_bold_font = this->fonts->get_small_bold_font(); + auto tiny_font = this->fonts->get_tiny_font(); + this->bar_width = + static_cast(std::ceil(width / HourlyForecastBox::visible_bars)); this->bars_height = this->bounding_box.h; this->frame_start_x = this->bounding_box.x; this->frame_start_y = this->bounding_box.y; - auto normal_font = this->fonts->get_normal_font(); - auto small_bold_font = this->fonts->get_small_bold_font(); - auto tiny_font = this->fonts->get_tiny_font(); - - this->time_y = this->frame_start_y + this->vertical_padding / 2; + this->date_labels_start_y = this->frame_start_y; + this->time_y = this->date_labels_start_y + tiny_font->height; this->icon_y = this->time_y + tiny_font->height; this->temperature_y = this->frame_start_y + this->bars_height - this->vertical_padding / 2 - normal_font->height - @@ -47,6 +46,7 @@ HourlyForecastBox::HourlyForecastBox(int pos_x, int pos_y, int width, void HourlyForecastBox::show() { this->fill_bounding_box(); + this->draw_labels(); this->draw_frame_and_values(); this->draw_precipitation_histogram(); this->draw_temperature_curve(); @@ -77,9 +77,10 @@ bool HourlyForecastBox::handle_key_release(int key) { void HourlyForecastBox::increase_forecast_offset() { const size_t max_forecast_offset{this->model->hourly_forecast.size() - - this->visible_bars}; + HourlyForecastBox::visible_bars}; const auto updated_forecast_offset = - std::min(this->forecast_offset + this->visible_bars, max_forecast_offset); + std::min(this->forecast_offset + HourlyForecastBox::visible_bars, + max_forecast_offset); if (updated_forecast_offset != this->forecast_offset) { this->forecast_offset = updated_forecast_offset; BOOST_LOG_TRIVIAL(debug) @@ -93,8 +94,8 @@ void HourlyForecastBox::increase_forecast_offset() { void HourlyForecastBox::decrease_forecast_offset() { const size_t min_forecast_offset{0}; const auto updated_forecast_offset = - (this->forecast_offset > this->visible_bars) - ? this->forecast_offset - this->visible_bars + (this->forecast_offset > HourlyForecastBox::visible_bars) + ? this->forecast_offset - HourlyForecastBox::visible_bars : min_forecast_offset; // don't use std::max since we're working with unsigned integers! @@ -116,6 +117,53 @@ void HourlyForecastBox::draw_and_update() { this->bounding_box.w, this->bounding_box.h); } +std::tuple +HourlyForecastBox::get_date_label_properties(size_t bar_index) const { + const int forecast_index = this->forecast_offset + bar_index; + if (forecast_index < this->model->hourly_forecast.size()) { + const auto forecast = this->model->hourly_forecast[forecast_index]; + if (bar_index == 0) { + return {format_date(forecast.date, true), + std::min(static_cast(HourlyForecastBox::visible_bars), + remaining_hours(forecast.date)) * + this->bar_width}; + } else if (bar_index == HourlyForecastBox::visible_bars - 1) { + return {format_date(forecast.date, true), + std::min(static_cast(HourlyForecastBox::visible_bars), + (passed_hours(forecast.date) + 1)) * + this->bar_width}; + } + } + return {"", 0}; +} + +void HourlyForecastBox::draw_labels() const { + auto tiny_font = this->fonts->get_tiny_font(); + SetFont(tiny_font.get(), BLACK); + + const auto [left_label_text, left_label_width] = + this->get_date_label_properties(0); + if (not left_label_text.empty() and left_label_width > 0) { + if (StringWidth(left_label_text.c_str()) < left_label_width) { + DrawTextRect(this->bounding_box.x, this->date_labels_start_y, + left_label_width, tiny_font->height, left_label_text.c_str(), + ALIGN_CENTER); + }; + } + + const auto [right_label_text, right_label_width] = + this->get_date_label_properties(HourlyForecastBox::visible_bars - 1); + if (not right_label_text.empty() and right_label_width > 0 and + right_label_text != left_label_text) { + if (StringWidth(right_label_text.c_str()) < right_label_width) { + DrawTextRect(this->bounding_box.x + this->bounding_box.w - + right_label_width, + this->date_labels_start_y, right_label_width, + tiny_font->height, right_label_text.c_str(), ALIGN_CENTER); + }; + } +} + void HourlyForecastBox::draw_frame_and_values() const { DrawLine(this->frame_start_x, this->frame_start_y, this->bounding_box.w, this->frame_start_y, LGRAY); @@ -127,18 +175,23 @@ void HourlyForecastBox::draw_frame_and_values() const { auto small_bold_font = this->fonts->get_small_bold_font(); auto tiny_font = this->fonts->get_tiny_font(); - const auto separator_start_y = this->frame_start_y; const auto separator_stop_y = this->frame_start_y + this->bars_height; const Units units{this->model}; - for (size_t bar_index = 0; bar_index < this->visible_bars; ++bar_index) { + for (size_t bar_index = 0; bar_index < HourlyForecastBox::visible_bars; + ++bar_index) { const auto bar_center_x = (bar_index + 1.0 / 2) * this->bar_width; + auto separator_start_y = this->frame_start_y; const auto forecast_index = this->forecast_offset + bar_index; if (forecast_index < this->model->hourly_forecast.size()) { const auto forecast = this->model->hourly_forecast[forecast_index]; + if (remaining_hours(forecast.date) > 1) { + separator_start_y += tiny_font->height; + } + SetFont(tiny_font.get(), BLACK); const auto time_text = format_time(forecast.date, true); @@ -167,7 +220,7 @@ void HourlyForecastBox::draw_frame_and_values() const { this->humidity_y, humidity_text.c_str()); } - if (bar_index < this->visible_bars - 1) { + if (bar_index < HourlyForecastBox::visible_bars - 1) { const auto separator_x = (bar_index + 1) * this->bar_width; DrawLine(separator_x, separator_start_y, separator_x, separator_stop_y, LGRAY); @@ -243,7 +296,8 @@ void HourlyForecastBox::draw_precipitation_histogram() const { SetFont(tiny_font.get(), DGRAY); - for (size_t bar_index = 0; bar_index < this->visible_bars; ++bar_index) { + for (size_t bar_index = 0; bar_index < HourlyForecastBox::visible_bars; + ++bar_index) { const auto forecast_index = this->forecast_offset + bar_index; if (forecast_index < this->model->hourly_forecast.size()) { const auto forecast = this->model->hourly_forecast[forecast_index]; diff --git a/src/hourlyforecastbox.h b/src/hourlyforecastbox.h index c5a7153..b134f96 100644 --- a/src/hourlyforecastbox.h +++ b/src/hourlyforecastbox.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "fonts.h" #include "icons.h" @@ -24,17 +25,18 @@ class HourlyForecastBox : public Widget { private: static constexpr int vertical_padding{25}; static constexpr int icon_size{100}; + static constexpr size_t visible_bars{8}; std::shared_ptr model; std::shared_ptr icons; std::shared_ptr fonts; - size_t visible_bars; int bar_width; int frame_start_x; int frame_start_y; + int date_labels_start_y; int time_y; int icon_y; int temperature_y; @@ -53,8 +55,13 @@ class HourlyForecastBox : public Widget { bool handle_key_release(int key) override; + std::tuple + get_date_label_properties(size_t bar_index) const; + void draw_and_update(); + void draw_labels() const; + void draw_frame_and_values() const; void draw_temperature_curve() const; diff --git a/src/keys.h b/src/keys.h index e937069..a18cfcf 100644 --- a/src/keys.h +++ b/src/keys.h @@ -75,7 +75,7 @@ struct KeyEventDispatcher { if (found != this->consumers.end()) { this->consumers.erase(found); if (this->last_event_consumer == consumer) { - this->last_event_consumer.reset(); + this->last_event_consumer.reset(); } } } diff --git a/src/util.cc b/src/util.cc index d55fcc7..188749f 100644 --- a/src/util.cc +++ b/src/util.cc @@ -1,11 +1,12 @@ #include "util.h" -#include "inkview.h" #include #include #include +#include #include #include +#include using namespace std::chrono_literals; @@ -107,32 +108,13 @@ double taranis::max_number(double value_one, double value_two) { } constexpr char time_format[] = "%H:%M"; -constexpr char full_date_format[] = "%R"; static char formatted_time[100]; -std::string taranis::format_full_date(const TimePoint &time) { - auto format = full_date_format; - time_t time_since_epoch{static_cast( - std::chrono::duration_cast(time - TimePoint{}) - .count())}; - auto calendar_time = std::localtime(&time_since_epoch); - if (calendar_time == nullptr) { - return ""; - } - std::strftime(formatted_time, sizeof(formatted_time), format, calendar_time); - // TODO should use GetLangTime() to use user "locale" but don't know - // how it works… - - return std::string{DateStr(time_since_epoch)} + ", " + - std::string{formatted_time}; -} - std::string taranis::format_time(const TimePoint &time, bool round) { - auto format = time_format; - time_t time_since_epoch{static_cast( + const time_t time_since_epoch{static_cast( std::chrono::duration_cast(time - TimePoint{}) .count())}; - auto calendar_time = std::localtime(&time_since_epoch); + const auto calendar_time = std::localtime(&time_since_epoch); if (calendar_time == nullptr) { return ""; } @@ -146,7 +128,8 @@ std::string taranis::format_time(const TimePoint &time, bool round) { calendar_time->tm_sec = 0; } } - std::strftime(formatted_time, sizeof(formatted_time), format, calendar_time); + std::strftime(formatted_time, sizeof(formatted_time), time_format, + calendar_time); // Should use GetLangTime() to use user "locale" but don't know how // it works… return formatted_time; @@ -156,6 +139,74 @@ const char *weekdays[7] = {"@Sun", "@Mon", "@Tue", "@Wed", "@Thu", "@Fri", "@Sat"}; std::string taranis::format_day(const TimePoint &time) { + const time_t time_since_epoch{static_cast( + std::chrono::duration_cast(time - TimePoint{}) + .count())}; + const auto calendar_time = std::localtime(&time_since_epoch); + if (calendar_time == nullptr) { + return ""; + } + return GetLangText(weekdays[calendar_time->tm_wday]); +} + +const char *months[12] = {"@Jan", "@Feb", "@Mar", "@Apr", "@May", "@Jun", + "@Jul", "@Aug", "@Sep", "@Oct", "@Nov", "@Dec"}; + +std::string taranis::format_short_date(const TimePoint &time) { + const time_t time_since_epoch{static_cast( + std::chrono::duration_cast(time - TimePoint{}) + .count())}; + const auto calendar_time = std::localtime(&time_since_epoch); + if (calendar_time == nullptr) { + return ""; + } + return std::to_string(calendar_time->tm_mday) + " " + + GetLangText(months[calendar_time->tm_mon]); +} + +std::string taranis::format_date(const TimePoint &time, bool shortcut) { + const time_t time_since_epoch{static_cast( + std::chrono::duration_cast(time - TimePoint{}) + .count())}; + if (shortcut) { + auto calendar_time = std::localtime(&time_since_epoch); + if (calendar_time) { + const auto time_year = calendar_time->tm_year; + const auto time_year_day = calendar_time->tm_yday; + const auto time_month = calendar_time->tm_mon; + const auto time_month_day = calendar_time->tm_mday; + + const time_t now_since_epoch{ + static_cast(std::chrono::duration_cast( + std::chrono::system_clock::now() - TimePoint{}) + .count())}; + calendar_time = std::localtime(&now_since_epoch); + if (calendar_time) { + const auto current_year = calendar_time->tm_year; + const auto current_year_day = calendar_time->tm_yday; + const auto current_month = calendar_time->tm_mon; + const auto current_month_day = calendar_time->tm_mday; + + if (current_year == time_year and current_year_day == time_year_day) { + return GetLangText("Today"); + } else if ((current_year == time_year and + current_year_day == time_year_day + 1) or + ((current_year == time_year + 1 and current_year_day == 0 and + time_month_day == 31 and time_month == 11))) { + return GetLangText("Yesterday"); + } else if ((current_year == time_year and + current_year_day + 1 == time_year_day) or + ((current_year + 1 == time_year and time_year_day == 0 and + current_month_day == 31 and current_month == 11))) { + return GetLangText("Tomorrow"); + } + } + } + } + return std::string{DateStr(time_since_epoch)}; +} + +std::string taranis::format_full_date(const TimePoint &time) { time_t time_since_epoch{static_cast( std::chrono::duration_cast(time - TimePoint{}) .count())}; @@ -163,7 +214,13 @@ std::string taranis::format_day(const TimePoint &time) { if (calendar_time == nullptr) { return ""; } - return GetLangText(weekdays[calendar_time->tm_wday]); + std::strftime(formatted_time, sizeof(formatted_time), time_format, + calendar_time); + // TODO should use GetLangTime() to use user "locale" but don't know + // how it works… + + return std::string{DateStr(time_since_epoch)} + ", " + + std::string{formatted_time}; } std::string taranis::format_duration(const TimePoint &start, @@ -182,19 +239,26 @@ std::string taranis::format_duration(const TimePoint &start, return duration_text.str(); } -const char *months[12] = {"@Jan", "@Feb", "@Mar", "@Apr", "@May", "@Jun", - "@Jul", "@Aug", "@Sep", "@Oct", "@Nov", "@Dec"}; +int taranis::remaining_hours(const TimePoint &time) { + time_t time_since_epoch{static_cast( + std::chrono::duration_cast(time - TimePoint{}) + .count())}; + auto calendar_time = std::localtime(&time_since_epoch); + if (calendar_time == nullptr) { + return 0; + } + return 24 - calendar_time->tm_hour; +} -std::string taranis::format_short_date(const TimePoint &time) { +int taranis::passed_hours(const TimePoint &time) { time_t time_since_epoch{static_cast( std::chrono::duration_cast(time - TimePoint{}) .count())}; auto calendar_time = std::localtime(&time_since_epoch); if (calendar_time == nullptr) { - return ""; + return 0; } - return std::to_string(calendar_time->tm_mday) + " " + - GetLangText(months[calendar_time->tm_mon]); + return calendar_time->tm_hour; } std::pair diff --git a/src/util.h b/src/util.h index 3de781c..1bd4edc 100644 --- a/src/util.h +++ b/src/util.h @@ -54,16 +54,27 @@ inline void rtrim(std::string &s) { s.end()); } +// 17:05 std::string format_time(const TimePoint &time, bool round = false); +// Saturday std::string format_day(const TimePoint &time); +// 21 Oct std::string format_short_date(const TimePoint &time); +// Saturday, 21st October 2023, Today or Tomorrow +std::string format_date(const TimePoint &time, bool shortcut = false); + +// Saturday, 21st October 2023, 17:05 std::string format_full_date(const TimePoint &time); std::string format_duration(const TimePoint &start, const TimePoint &end); +int remaining_hours(const TimePoint &time); + +int passed_hours(const TimePoint &time); + inline std::string format_location(const std::string &town, const std::string &country) { std::string text = town;