WorldClock: Create day/night shadow 12/96612/12
authorKamil Lipiszko <k.lipiszko@samsung.com>
Fri, 4 Nov 2016 12:42:19 +0000 (13:42 +0100)
committerLukasz Stanislawski <l.stanislaws@samsung.com>
Thu, 1 Dec 2016 11:35:09 +0000 (03:35 -0800)
Creates day and night shadow with line in WorldClock view.

Change-Id: I28a84f072051bb265034edacc7909538f89616c4

clock/inc/View/WorldClockMap.h [new file with mode: 0644]
clock/inc/View/WorldClockView.h
clock/res/edje/WorldClock.edc
clock/src/View/WorldClockMap.cpp [new file with mode: 0644]
clock/src/View/WorldClockView.cpp

diff --git a/clock/inc/View/WorldClockMap.h b/clock/inc/View/WorldClockMap.h
new file mode 100644 (file)
index 0000000..aa2c6f6
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+* Copyright 2016  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef _CLOCK_VIEW_WORLD_MAP_H_
+#define _CLOCK_VIEW_WORLD_MAP_H_
+
+#include <Elementary.h>
+#include <system_settings.h>
+#include <cairo.h>
+#include <vector>
+#include <ctime>
+
+#include "View/View.h"
+
+namespace view {
+
+class WorldClockMap : public ui::IView {
+public:
+
+       Evas_Object *GetEvasObject();
+       WorldClockMap(ui::IView &parent);
+       ~WorldClockMap();
+
+private:
+
+       Evas_Object *map_;
+       Evas_Object *scroller_;
+       Evas_Object *line_scroller_;
+       Evas_Object *shadow_;
+       Evas_Object *line_;
+       Ecore_Timer *timer_;
+
+       cairo_t *line_cairo_;
+       cairo_surface_t *line_surface_;
+
+       cairo_t *shadow_cairo_;
+       cairo_surface_t *shadow_surface_;
+
+       int mapX = -1, mapY = -1, mapW = -1, mapH = -1;
+       int currentYDay = -1;
+
+       static const int LATITUDE_RESOLUTION_ = 181;
+       std::vector<int> day_times_ = std::vector<int>(LATITUDE_RESOLUTION_, 0);
+
+       /**
+        * @brief Returns today's sun declination angle.
+        *
+        * During the Earth movement, sun's position on the horizon
+        * changes from -23.44 to +23.44 degree. This value stores the
+        * angle for current day.
+        *
+        * @param now Current time structure.
+        *
+        * @return Sun declination angle in degrees.
+        *
+        * @note Returned value is double so for example 21o15' is
+        *      21.25 in floating point value.
+        */
+       double SunDeclination(std::tm now);
+
+       /**
+        * @brief Returns angle of the time where currently is day.
+        *
+        * @param latitude The latitude
+        * @param declination The sun declination of current day.
+        *
+        * @return Angle in degrees.
+        *
+        * @see GetSunDeclination()
+        */
+       double TimeAngle(int latitude, double declination);
+
+       /**
+        * @brief Requests for map update.
+        *
+        */
+       void MapUpdate();
+
+       /**
+        * @brief Draws night shadow with day/night line.
+        *
+        * @param now The current time structure
+        * @param declination The declination of the current day
+        *
+        * @return True on success, false on failure.
+        */
+       bool ShadowCreate(std::tm now, double declination);
+
+       /**
+        * @brief Creates night shadow.
+        */
+       void ShadowDraw();
+
+       /**
+        * @brief Creates day/night line.
+        */
+       void LineDraw();
+
+       /**
+        * @brief Creates scroller for night shadow.
+        */
+       void ShadowScrollerCreate();
+
+       /**
+        * @brief Creates scroller for day/night line.
+        */
+       void LineScrollerCreate();
+
+       /**
+        * @brief Creates scroller.
+        *
+        * @param part The parent's swallow part.
+        *
+        * @return The Evas_Object of created scroller.
+        */
+       Evas_Object *ScrollerCreate();
+
+       /**
+        * @brief Changes scroller's visible area position.
+        *
+        * @param minutes The move offset
+        */
+       void ShadowMove(int minutes);
+
+       /**
+        * @brief Task to be done on particular time update request.
+        */
+       static Eina_Bool TimerCb(void *data);
+
+       /**
+        * @brief Callback called on properly shown map image.
+        */
+       static void ScrollerResizeCb(void *data, Evas *e, Evas_Object *obj, void *event_info);
+
+       /**
+        * @brief Callback called on time or timezone change.
+        */
+       static void TimeChangedCb(system_settings_key_e key, void *user_data);
+
+};
+
+}
+
+#endif //_CLOCK_VIEW_WORLD_MAP_H_
index 9b2bcad..925ffc4 100644 (file)
@@ -23,6 +23,7 @@
 #include "Model/WorldClock.h"
 #include "View/PageView.h"
 #include "View/View.h"
+#include "View/WorldClockMap.h"
 
 namespace view {
 
@@ -154,6 +155,7 @@ namespace view {
                         * @brief Timer for time and date updating
                         */
                        Ecore_Timer *timer_;
+                       WorldClockMap *map_;
 
                        std::vector<std::function<void(void)>> signals
                                                = std::vector<std::function<void(void)>>((int)WorldClockSignals::MAX, nullptr);
index 20aa8a8..120189c 100644 (file)
@@ -68,6 +68,7 @@ collections {
                image: "clock_icon_world_clock_arrow_right.png" COMP;
                image: "clock_world_location_dot.png" COMP;
                image: "clock_world_location_ring.png" COMP;
+               image: "clock_world_map_01.png" COMP;
        }
 
        group { name: "main";
@@ -126,13 +127,12 @@ collections {
                        }
                }
                parts {
-                       image { "map"; scale;
-                       desc { "default";
-                               min: 720 406;
-                               max: 720 406;
-                               fixed: 1 1;
-                               image.normal: "clock_world_map_01.png";
-                       }
+                       swallow { "map"; scale;
+                               desc { "default";
+                                       min: 720 406;
+                                       max: 720 406;
+                                       fixed: 1 1;
+                               }
                        }
 
                        image { "timezone.area"; scale;
@@ -536,5 +536,50 @@ collections {
                        }
                }
        }
+
+       group { "map";
+               parts {
+                       spacer { "pd.top"; scale;
+                               desc {
+                                       align: 0.5 0.0;
+                                       min: 0 92;
+                                       max: -1 92;
+                                       fixed: 0 1;
+                               }
+                       }
+                       spacer { "pd.bottom"; scale;
+                               desc {
+                                       align: 0.5 1.0;
+                                       min: 0 0;
+                                       max: -1 0;
+                                       fixed: 0 1;
+                               }
+                       }
+                       swallow { "line_scroller";
+                               desc {
+                                       rel1 { relative: 0.0 1.0; to_y: "pd.top"; }
+                                       rel2 { relative: 1.0 0.0; to_y: "pd.bottom"; }
+                               }
+                       }
+                       image { "clipper";
+                               desc {
+                                       image.normal: "clock_world_map_01.png";
+                               }
+                       }
+                       image { "map";
+                               desc {
+                                       image.normal: "clock_world_map_01.png";
+                                       color: 32 97 104 255;
+                               }
+                       }
+                       swallow { "scroller";
+                               clip_to: "clipper";
+                               desc {
+                                       rel1 { relative: 0.0 1.0; to_y: "pd.top"; }
+                                       rel2 { relative: 1.0 0.0; to_y: "pd.bottom"; }
+                               }
+                       }
+               }
+       }
 }
 
diff --git a/clock/src/View/WorldClockMap.cpp b/clock/src/View/WorldClockMap.cpp
new file mode 100644 (file)
index 0000000..5e48db1
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+* Copyright 2016  Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include <cmath>
+
+#include "View/WorldClockMap.h"
+#include "Utils/Utils.h"
+#include "Utils/Log.h"
+#include "Utils/Time.h"
+
+namespace view {
+
+using namespace utils;
+
+WorldClockMap::WorldClockMap(ui::IView &parent)
+       :  map_(nullptr), shadow_(nullptr), line_(nullptr), timer_(nullptr)
+{
+       map_ = elm_layout_add(parent.GetEvasObject());
+       if (!elm_layout_file_set(map_,
+                       Utils::GetAppResourcePath(Utils::APP_DIR_RESOURCE, "edje/WorldClock.edj"),
+                       "map"))
+               FAT("elm_layout_file_set failed");
+
+       evas_object_size_hint_align_set(map_, EVAS_HINT_FILL, EVAS_HINT_FILL);
+       evas_object_size_hint_weight_set(map_, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+       evas_object_show(map_);
+
+       ShadowScrollerCreate();
+       /*
+        * Line should not be clipped, moreover it is below the map layer so that is why used
+        * separate scroller.
+        */
+       LineScrollerCreate();
+
+       system_settings_set_changed_cb(SYSTEM_SETTINGS_KEY_TIME_CHANGED, TimeChangedCb, this);
+       system_settings_set_changed_cb(SYSTEM_SETTINGS_KEY_LOCALE_TIMEZONE, TimeChangedCb, this);
+}
+
+WorldClockMap::~WorldClockMap()
+{
+}
+
+void WorldClockMap::ShadowScrollerCreate()
+{
+       scroller_ = ScrollerCreate();
+       elm_object_part_content_set(map_, "scroller", scroller_);
+
+       evas_object_event_callback_add(scroller_, EVAS_CALLBACK_MOVE, ScrollerResizeCb, this);
+}
+
+void WorldClockMap::LineScrollerCreate()
+{
+       line_scroller_ = ScrollerCreate();
+       elm_object_part_content_set(map_, "line_scroller", line_scroller_);
+}
+
+Evas_Object *WorldClockMap::ScrollerCreate()
+{
+       Evas_Object *scroller = elm_scroller_add(map_);
+       evas_object_size_hint_align_set(scroller, EVAS_HINT_FILL, EVAS_HINT_FILL);
+       evas_object_size_hint_weight_set(scroller, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+       elm_scroller_policy_set(scroller, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_OFF);
+       elm_scroller_movement_block_set(scroller, (Elm_Scroller_Movement_Block)
+                       (ELM_SCROLLER_MOVEMENT_BLOCK_HORIZONTAL | ELM_SCROLLER_MOVEMENT_BLOCK_VERTICAL));
+       elm_scroller_loop_set(scroller, EINA_TRUE, EINA_FALSE);
+
+       evas_object_show(scroller);
+
+       Evas_Object *box = elm_box_add(map_);
+       elm_box_horizontal_set(box, EINA_TRUE);
+       elm_box_homogeneous_set(box, EINA_TRUE);
+       evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+       evas_object_size_hint_align_set(box, EVAS_HINT_FILL, EVAS_HINT_FILL);
+       evas_object_show(box);
+
+       elm_object_content_set(scroller, box);
+
+       return scroller;
+}
+
+void WorldClockMap::ScrollerResizeCb(void *data, Evas *e, Evas_Object *obj, void *event_info)
+{
+       WorldClockMap *view = static_cast<WorldClockMap *>(data);
+       view->MapUpdate();
+}
+
+void WorldClockMap::TimeChangedCb(system_settings_key_e key, void *user_data)
+{
+       if (key != SYSTEM_SETTINGS_KEY_LOCALE_TIMEZONE && key != SYSTEM_SETTINGS_KEY_TIME_CHANGED)
+               return;
+
+       WorldClockMap *view = static_cast<WorldClockMap *>(user_data);
+       view->MapUpdate();
+}
+
+void WorldClockMap::MapUpdate()
+{
+       std::time_t now = time(NULL);
+       std::tm *utcTime = gmtime(&now);
+
+       if (utcTime->tm_yday != currentYDay) {
+               double declination = SunDeclination(*utcTime);
+               if (!ShadowCreate(*utcTime, declination))
+                       return;
+
+               currentYDay = utcTime->tm_yday;
+       }
+
+       int timeDiff = (utcTime->tm_hour * 60 + utcTime->tm_min) - 12 * 60;
+       ShadowMove(timeDiff);
+}
+
+void WorldClockMap::ShadowMove(int minutes)
+{
+       int y, w, h;
+       int x = minutes/(24.0 * 60.0) * mapW;
+
+       x = x < 0 ? mapW + x : x; //moving -100px is the same as 720 - 100 = 620 px
+
+       elm_scroller_region_get(scroller_, NULL, &y, &w, &h);
+       elm_scroller_region_show(scroller_, x, y, w, h);
+
+       elm_scroller_region_get(line_scroller_, NULL, &y, &w, &h);
+       elm_scroller_region_show(line_scroller_, x, y, w, h);
+}
+
+Evas_Object *WorldClockMap::GetEvasObject()
+{
+       return map_;
+}
+
+bool WorldClockMap::ShadowCreate(std::tm now, double declination)
+{
+       evas_object_geometry_get(scroller_, &mapX, &mapY, &mapW, &mapH);
+
+       if (mapX < 0 || mapY < 0 || mapW <= 0 || mapH <= 0)
+               return false;
+       ++mapW; //evas_object_geometry_get returns item's width - 1px
+
+       for (int i = 0; i < LATITUDE_RESOLUTION_; ++i) {
+               double time = TimeAngle(90 - i, declination);
+
+               int mapLength = time/360.0 * mapW;
+               day_times_[i] = mapLength;
+       }
+
+       ShadowDraw();
+       LineDraw();
+
+       if (timer_)
+               ecore_timer_del(timer_);
+
+       timer_ = ecore_timer_add(60.0, TimerCb, this);
+       if (!timer_)
+               ERR("ecore_timer_add failed");
+
+       return true;
+}
+
+void WorldClockMap::ShadowDraw()
+{
+       if (shadow_) {
+               evas_object_del(shadow_);
+
+               cairo_destroy(shadow_cairo_);
+               cairo_surface_destroy(shadow_surface_);
+       }
+
+       shadow_ = evas_object_image_filled_add(evas_object_evas_get(scroller_));
+       evas_object_image_alpha_set(shadow_, EINA_TRUE);
+       evas_object_show(shadow_);
+
+       evas_object_geometry_set(shadow_, mapX, mapY, mapW, mapH);
+       evas_object_image_size_set(shadow_, mapW, mapH);
+
+       shadow_surface_ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, mapW, mapH);
+       shadow_cairo_ = cairo_create(shadow_surface_);
+
+       cairo_set_source_rgba(shadow_cairo_, 1.0, 1.0, 1.0, 1.0);
+       cairo_set_line_width(shadow_cairo_, 1.0);
+
+       for (auto it = day_times_.begin(); it != day_times_.end(); ++it)
+               cairo_line_to(shadow_cairo_, (mapW - *it)/2.0, ((it - day_times_.begin())/180.0 * mapH));
+
+       int idx = LATITUDE_RESOLUTION_ - 1;
+       for (auto it = day_times_.rbegin(); it != day_times_.rend(); ++it, --idx)
+               cairo_line_to(shadow_cairo_, (mapW + *it)/2.0, (idx/180.0 * mapH));
+
+       cairo_close_path(shadow_cairo_);
+       cairo_fill(shadow_cairo_);
+
+       cairo_stroke(shadow_cairo_);
+       cairo_surface_flush(shadow_surface_);
+
+       unsigned char *imageData = cairo_image_surface_get_data(cairo_get_target(shadow_cairo_));
+       evas_object_image_data_set(shadow_, imageData);
+       evas_object_image_data_update_add(shadow_, 0, 0, mapW, mapH);
+
+       evas_object_size_hint_weight_set(shadow_, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+       evas_object_size_hint_align_set(shadow_, EVAS_HINT_FILL, EVAS_HINT_FILL);
+
+       Evas_Object *box = elm_object_content_get(scroller_);
+       elm_box_pack_end(box, shadow_);
+}
+
+void WorldClockMap::LineDraw()
+{
+       if (line_) {
+               evas_object_del(line_);
+
+               cairo_destroy(line_cairo_);
+               cairo_surface_destroy(line_surface_);
+       }
+
+       line_ = evas_object_image_filled_add(evas_object_evas_get(line_scroller_));
+       evas_object_image_alpha_set(line_, EINA_TRUE);
+       evas_object_show(line_);
+
+       evas_object_geometry_set(line_, mapX, mapY, mapW, mapH);
+       evas_object_image_size_set(line_, mapW, mapH);
+
+       line_surface_ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, mapW, mapH);
+       line_cairo_ = cairo_create(line_surface_);
+       cairo_paint_with_alpha(line_cairo_, 0.0);
+
+       cairo_set_source_rgba(line_cairo_, 1.0, 1.0, 1.0, 0.5);
+       cairo_set_line_width(line_cairo_, 3.0);
+
+       for (auto it = day_times_.begin(); it != day_times_.end(); ++it) {
+               if (*it <= 0 || *it >= mapW) {
+                       if ((it - day_times_.begin()) > LATITUDE_RESOLUTION_/2.0) {
+                               cairo_line_to(line_cairo_, (mapW - *it)/2.0,
+                                                       ((it - day_times_.begin())/180.0 * mapH));
+                               break;
+                       }
+                       cairo_move_to(line_cairo_, (mapW - *it)/2.0,
+                                               ((it - day_times_.begin())/180.0 * mapH));
+               } else
+                       cairo_line_to(line_cairo_, (mapW - *it)/2.0, ((it - day_times_.begin())/180.0 * mapH));
+       }
+
+
+       int idx = LATITUDE_RESOLUTION_ - 1;
+       cairo_move_to(line_cairo_, (mapW + *day_times_.rbegin())/2.0, (idx/180.0 * mapH));
+       for (auto it = day_times_.rbegin(); it != day_times_.rend(); ++it, --idx) {
+               if (*it <= 0 || *it >= mapW) {
+                       if (idx < LATITUDE_RESOLUTION_/2.0) {
+                               cairo_line_to(line_cairo_, (mapW + *it)/2.0, (idx/180.0 * mapH));
+                               break;
+                       }
+                       cairo_move_to(line_cairo_, (mapW + *it)/2.0, (idx/180.0 * mapH));
+               } else
+                       cairo_line_to(line_cairo_, (mapW + *it)/2.0, (idx/180.0 * mapH));
+       }
+
+       cairo_stroke(line_cairo_);
+       cairo_surface_flush(line_surface_);
+
+       unsigned char *imageData = cairo_image_surface_get_data(cairo_get_target(line_cairo_));
+       evas_object_image_data_set(line_, imageData);
+       evas_object_image_data_update_add(line_, 0, 0, mapW, mapH);
+
+       evas_object_size_hint_weight_set(line_, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+       evas_object_size_hint_align_set(line_, EVAS_HINT_FILL, EVAS_HINT_FILL);
+
+       Evas_Object *box = elm_object_content_get(line_scroller_);
+       elm_box_pack_end(box, line_);
+}
+
+Eina_Bool WorldClockMap::TimerCb(void *data)
+{
+       WorldClockMap *view = static_cast<WorldClockMap *>(data);
+       view->MapUpdate();
+
+       return ECORE_CALLBACK_RENEW;
+}
+
+/**
+ * M_PI/180.0 is to convert angle from degrees to radians and 180.0/M_PI is in
+ * opposite direction. Tw is the angle from 0 to sunrise or sunset. The sunrise
+ * is 12h - Tw and sunset 12h + Tw. Because returned value is in degrees the
+ * total angle from sunrise to sunset is uqual 2Tw.
+ */
+double WorldClockMap::TimeAngle(int latitude, double declination)
+{
+       double cosTw = std::tan((declination * M_PI/180)) * std::tan(latitude * M_PI/180);
+
+       if (cosTw <= -1.0)
+               return 360.0;
+       else if (cosTw >= 1.0)
+               return 0.0;
+
+       double Tw = std::acos(cosTw) * 180.0/M_PI;
+
+       return 2 * Tw;
+}
+
+double WorldClockMap::SunDeclination(std::tm now)
+{
+       return 23.44 * std::cos((360.0 / 365.0) * (now.tm_yday + 10) * M_PI/180);
+}
+
+}
index 59f5928..cf2ccbd 100644 (file)
@@ -285,11 +285,16 @@ WorldClockView::WorldClockView(ui::IView &main)
        timer_ = ecore_timer_add(time_delta, WorldClockView::TimeUpdateCb, this);
        if (!timer_)
                FAT("Unable to create timer!");
+
+       map_ = new WorldClockMap(*this);
+       elm_object_part_content_set(world_clock_, "main.world.map:map", map_->GetEvasObject());
 }
 
 WorldClockView::~WorldClockView()
 {
        ecore_timer_del(timer_);
+
+       delete map_;
 }
 
 void WorldClockView::MoreButtonClicked(void *data, Evas_Object *obj, void *info)