Skip to content

Commit

Permalink
Add labels to identify hourly forecast dates
Browse files Browse the repository at this point in the history
Refs: #62
  • Loading branch information
orontee committed Oct 21, 2023
1 parent 71d3673 commit 0e5e498
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 48 deletions.
86 changes: 70 additions & 16 deletions src/hourlyforecastbox.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,19 @@ HourlyForecastBox::HourlyForecastBox(int pos_x, int pos_y, int width,
std::shared_ptr<Fonts> fonts)
: Widget{pos_x, pos_y, width, height}, model{model}, icons{icons},
fonts{fonts} {
this->visible_bars = 8;

this->bar_width = static_cast<int>(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<int>(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 -
Expand All @@ -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();
Expand Down Expand Up @@ -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)
Expand All @@ -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!
Expand All @@ -116,6 +117,53 @@ void HourlyForecastBox::draw_and_update() {
this->bounding_box.w, this->bounding_box.h);
}

std::tuple<std::string, int>
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<int>(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<int>(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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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];
Expand Down
9 changes: 8 additions & 1 deletion src/hourlyforecastbox.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <memory>
#include <tuple>

#include "fonts.h"
#include "icons.h"
Expand All @@ -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> model;
std::shared_ptr<Icons> icons;
std::shared_ptr<Fonts> 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;
Expand All @@ -53,8 +55,13 @@ class HourlyForecastBox : public Widget {

bool handle_key_release(int key) override;

std::tuple<std::string, int>
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;
Expand Down
2 changes: 1 addition & 1 deletion src/keys.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
Expand Down
124 changes: 94 additions & 30 deletions src/util.cc
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#include "util.h"
#include "inkview.h"

#include <algorithm>
#include <boost/log/trivial.hpp>
#include <cassert>
#include <chrono>
#include <cmath>
#include <cstring>
#include <inkview.h>

using namespace std::chrono_literals;

Expand Down Expand Up @@ -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<long>(
std::chrono::duration_cast<std::chrono::seconds>(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<long>(
const time_t time_since_epoch{static_cast<long>(
std::chrono::duration_cast<std::chrono::seconds>(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 "";
}
Expand All @@ -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;
Expand All @@ -156,14 +139,88 @@ 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<long>(
std::chrono::duration_cast<std::chrono::seconds>(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<long>(
std::chrono::duration_cast<std::chrono::seconds>(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<long>(
std::chrono::duration_cast<std::chrono::seconds>(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<long>(std::chrono::duration_cast<std::chrono::seconds>(
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<long>(
std::chrono::duration_cast<std::chrono::seconds>(time - TimePoint{})
.count())};
auto calendar_time = std::localtime(&time_since_epoch);
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,
Expand All @@ -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<long>(
std::chrono::duration_cast<std::chrono::seconds>(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<long>(
std::chrono::duration_cast<std::chrono::seconds>(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<std::string, std::string>
Expand Down
Loading

0 comments on commit 0e5e498

Please sign in to comment.