From 1b56d130dbb09ffe47eb1bdfbe9f3d7da0338f55 Mon Sep 17 00:00:00 2001 From: ExMix Date: Sat, 21 Feb 2015 20:19:33 +0300 Subject: [PATCH] [drape] gui skin --- data/resource-default/default.ui | 50 +++ drape_frontend/drape_frontend.pro | 2 + .../drape_frontend_tests.pro | 8 +- .../drape_frontend_tests/gui_skin_tests.cpp | 60 ++++ drape_frontend/gui_skin.cpp | 320 ++++++++++++++++++ drape_frontend/gui_skin.hpp | 70 ++++ 6 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 data/resource-default/default.ui create mode 100644 drape_frontend/drape_frontend_tests/gui_skin_tests.cpp create mode 100644 drape_frontend/gui_skin.cpp create mode 100644 drape_frontend/gui_skin.hpp diff --git a/data/resource-default/default.ui b/data/resource-default/default.ui new file mode 100644 index 0000000000..08991246e6 --- /dev/null +++ b/data/resource-default/default.ui @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/drape_frontend/drape_frontend.pro b/drape_frontend/drape_frontend.pro index a0183c5e5a..04d373ab1f 100644 --- a/drape_frontend/drape_frontend.pro +++ b/drape_frontend/drape_frontend.pro @@ -40,6 +40,7 @@ SOURCES += \ map_data_provider.cpp \ user_mark_shapes.cpp \ user_marks_provider.cpp \ + gui_skin.cpp \ HEADERS += \ engine_context.hpp \ @@ -78,3 +79,4 @@ HEADERS += \ map_data_provider.hpp \ user_mark_shapes.hpp \ user_marks_provider.hpp \ + gui_skin.hpp \ diff --git a/drape_frontend/drape_frontend_tests/drape_frontend_tests.pro b/drape_frontend/drape_frontend_tests/drape_frontend_tests.pro index 980cee98bf..ea7c8fd2dc 100644 --- a/drape_frontend/drape_frontend_tests/drape_frontend_tests.pro +++ b/drape_frontend/drape_frontend_tests/drape_frontend_tests.pro @@ -4,12 +4,18 @@ CONFIG += console warn_on CONFIG -= app_bundle TEMPLATE = app -DEPENDENCIES = drape_frontend drape base fribidi +DEPENDENCIES = drape_frontend coding platform drape base fribidi expat ROOT_DIR = ../.. include($$ROOT_DIR/common.pri) +macx-* { + LIBS *= "-framework CoreLocation" "-framework Foundation" "-framework CoreWLAN" \ + "-framework QuartzCore" "-framework IOKit" +} + SOURCES += \ ../../testing/testingmain.cpp \ memory_feature_index_tests.cpp \ fribidi_tests.cpp \ object_pool_tests.cpp \ + gui_skin_tests.cpp \ diff --git a/drape_frontend/drape_frontend_tests/gui_skin_tests.cpp b/drape_frontend/drape_frontend_tests/gui_skin_tests.cpp new file mode 100644 index 0000000000..13292aeaf6 --- /dev/null +++ b/drape_frontend/drape_frontend_tests/gui_skin_tests.cpp @@ -0,0 +1,60 @@ +#include "../../base/SRC_FIRST.hpp" +#include "../../testing/testing.hpp" + +#include "../gui_skin.hpp" +#include "../visual_params.hpp" + +UNIT_TEST(ParseDefaultSkinTest) +{ + df::VisualParams::Init(2.0f, 1024); + df::GuiSkin skin(df::ResolveGuiSkinFile("default")); + float width = 600.0f; + float height = 800.0f; + skin.Resize(width, height); + + df::GuiPosition compassPos = skin.ResolvePosition(df::GuiSkin::GuiElement::Compass); + TEST_EQUAL(compassPos.m_anchor, dp::Center, ()); + TEST_ALMOST_EQUAL(compassPos.m_pixelPivot.x, 27.0f * 2.0f, ()); + TEST_ALMOST_EQUAL(compassPos.m_pixelPivot.y, height - 57.0f * 2.0f, ()); + + df::GuiPosition rulerPos = skin.ResolvePosition(df::GuiSkin::GuiElement::Ruler); + TEST_EQUAL(rulerPos.m_anchor, dp::Right, ()); + TEST_ALMOST_EQUAL(rulerPos.m_pixelPivot.x, width - 6.0f * 2.0f, ()); + TEST_ALMOST_EQUAL(rulerPos.m_pixelPivot.y, height - 42.0f * 2.0f, ()); + + df::GuiPosition copyRightPos = skin.ResolvePosition(df::GuiSkin::GuiElement::Copyright); + TEST_EQUAL(copyRightPos.m_anchor, dp::Right, ()); + TEST_ALMOST_EQUAL(copyRightPos.m_pixelPivot.x, width - 6.0f * 2.0f, ()); + TEST_ALMOST_EQUAL(copyRightPos.m_pixelPivot.y, height - 42.0f * 2.0f, ()); + + df::GuiPosition countryStatusPos = skin.ResolvePosition(df::GuiSkin::GuiElement::CountryStatus); + TEST_EQUAL(countryStatusPos.m_anchor, dp::Center, ()); + TEST_ALMOST_EQUAL(countryStatusPos.m_pixelPivot.x, width / 2.0f, ()); + TEST_ALMOST_EQUAL(countryStatusPos.m_pixelPivot.y, height / 2.0f, ()); + + { + width = 800.0f; + height = 600.0f; + skin.Resize(width, height); + + df::GuiPosition compassPos = skin.ResolvePosition(df::GuiSkin::GuiElement::Compass); + TEST_EQUAL(compassPos.m_anchor, dp::Center, ()); + TEST_ALMOST_EQUAL(compassPos.m_pixelPivot.x, 18.0f * 2.0f, ()); + TEST_ALMOST_EQUAL(compassPos.m_pixelPivot.y, height - 11.4f * 2.0f, ()); + + df::GuiPosition rulerPos = skin.ResolvePosition(df::GuiSkin::GuiElement::Ruler); + TEST_EQUAL(rulerPos.m_anchor, dp::Right, ()); + TEST_ALMOST_EQUAL(rulerPos.m_pixelPivot.x, width - 70.4f * 2.0f, ()); + TEST_ALMOST_EQUAL(rulerPos.m_pixelPivot.y, height - 10.5f * 2.0f, ()); + + df::GuiPosition copyRightPos = skin.ResolvePosition(df::GuiSkin::GuiElement::Copyright); + TEST_EQUAL(copyRightPos.m_anchor, dp::Right, ()); + TEST_ALMOST_EQUAL(copyRightPos.m_pixelPivot.x, width - 70.4f * 2.0f, ()); + TEST_ALMOST_EQUAL(copyRightPos.m_pixelPivot.y, height - 10.5f * 2.0f, ()); + + df::GuiPosition countryStatusPos = skin.ResolvePosition(df::GuiSkin::GuiElement::CountryStatus); + TEST_EQUAL(countryStatusPos.m_anchor, dp::Center, ()); + TEST_ALMOST_EQUAL(countryStatusPos.m_pixelPivot.x, width / 2.0f, ()); + TEST_ALMOST_EQUAL(countryStatusPos.m_pixelPivot.y, height / 2.0f, ()); + } +} diff --git a/drape_frontend/gui_skin.cpp b/drape_frontend/gui_skin.cpp new file mode 100644 index 0000000000..085ebf72a6 --- /dev/null +++ b/drape_frontend/gui_skin.cpp @@ -0,0 +1,320 @@ +#include "gui_skin.hpp" +#include "visual_params.hpp" + +#include "../base/string_utils.hpp" +#include "../coding/parse_xml.hpp" +#include "../platform/platform.hpp" + +#include "../std/function.hpp" + +namespace df +{ + +namespace +{ + +bool IsSimple(dp::Anchor anchor) +{ + return anchor >= 0 && anchor <= 8; +} + +bool IsAnchor(dp::Anchor anchor) +{ + return anchor >= 0 && anchor <= 10; +} + +dp::Anchor ParseValueAnchor(string const & value) +{ + if (value == "center") + return dp::Center; + else if (value == "left") + return dp::Left; + else if (value == "right") + return dp::Right; + else if (value == "top") + return dp::Top; + else if (value == "bottom") + return dp::Bottom; + else + ASSERT(false, ()); + + return dp::Center; +} + +dp::Anchor MergeAnchors(dp::Anchor src, dp::Anchor dst) +{ + ASSERT(IsSimple(dst), ()); + ASSERT(IsSimple(src), ()); + dp::Anchor result = static_cast(src | dst); + ASSERT(IsAnchor(result), ()); + return result; +} + +float ParseFloat(string const & v) +{ + double d = 0.0f; + VERIFY(strings::to_double(v, d), ()); + return d; +} + +class ResolverParser +{ +public: + enum class Element + { + Empty, + Anchor, + Relative, + Offset + }; + + ResolverParser() + : m_element(Element::Empty) {} + + void Parse(string const & attr, string const & value) + { + ASSERT(m_element != Element::Empty, ()); + + if (attr == "x") + { + ASSERT(m_element == Element::Offset, ()); + m_resolver.SetOffsetX(ParseFloat(value)); + } + else if (attr == "y") + { + ASSERT(m_element == Element::Offset, ()); + m_resolver.SetOffsetY(ParseFloat(value)); + } + else if (attr == "vertical") + { + if (m_element == Element::Anchor) + m_resolver.SetAnchorVertical(ParseValueAnchor(value)); + else if (m_element == Element::Relative) + m_resolver.SetRelativeVertical(ParseValueAnchor(value)); + else + ASSERT(false, ()); + } + else if (attr == "horizontal") + { + if (m_element == Element::Anchor) + m_resolver.SetAnchorHorizontal(ParseValueAnchor(value)); + else if (m_element == Element::Relative) + m_resolver.SetRelativeHorizontal(ParseValueAnchor(value)); + else + ASSERT(false, ()); + } + } + + void Reset() + { + m_resolver = PositionResolver(); + } + + PositionResolver const & GetResolver() const + { + return m_resolver; + } + + void SetElement(Element e) + { + m_element = e; + } + +private: + Element m_element; + PositionResolver m_resolver; +}; + +class SkinLoader +{ +public: + SkinLoader(map > & skin) + : m_skin(skin) {} + + bool Push(string const & element) + { + if (m_inElement == false) + { + if (element == "root") + return true; + + m_inElement = true; + if (element == "ruler") + m_currentElement = GuiSkin::GuiElement::Ruler; + else if (element == "compass") + m_currentElement = GuiSkin::GuiElement::Compass; + else if (element == "copyright") + m_currentElement = GuiSkin::GuiElement::Copyright; + else if (element == "country_status") + m_currentElement = GuiSkin::GuiElement::CountryStatus; + else + ASSERT(false, ()); + } + else if (m_inConfiguration == false) + { + if (element == "portrait" || element == "landscape") + m_inConfiguration = true; + else + ASSERT(false, ()); + } + else + { + if (element == "anchor") + m_parser.SetElement(ResolverParser::Element::Anchor); + else if (element == "relative") + m_parser.SetElement(ResolverParser::Element::Relative); + else if (element == "offset") + m_parser.SetElement(ResolverParser::Element::Offset); + else + ASSERT(false, ()); + } + + return true; + } + + void Pop(string const & element) + { + if (element == "anchor" || element == "relative" || element == "offset") + m_parser.SetElement(ResolverParser::Element::Empty); + else if (element == "portrait") + { + m_skin[m_currentElement].first = m_parser.GetResolver(); + m_parser.Reset(); + m_inConfiguration = false; + } + else if (element == "landscape") + { + m_skin[m_currentElement].second = m_parser.GetResolver(); + m_parser.Reset(); + m_inConfiguration = false; + } + else if (element == "ruler" || element == "compass" || + element == "copyright" || element == "country_status") + { + m_inElement = false; + } + } + + void AddAttr(string const & attribute, string const & value) + { + m_parser.Parse(attribute, value); + } + + void CharData(string const &) {} + +private: + bool m_inConfiguration = false; + bool m_inElement = false; + + GuiSkin::GuiElement m_currentElement = GuiSkin::GuiElement::Ruler; + ResolverParser m_parser; + + map > & m_skin; +}; + +} + +GuiPosition PositionResolver::Resolve(int w, int h) const +{ + float resultX = w / 2.0f; + float resultY = h / 2.0f; + m2::PointF offset = m_offset * df::VisualParams::Instance().GetVisualScale(); + + if (m_resolveAnchor & dp::Left) + resultX = offset.x; + else if (m_resolveAnchor & dp::Right) + resultX = w - offset.x; + else + resultX += offset.x; + + if (m_resolveAnchor & dp::Top) + resultY = offset.y; + else if (m_resolveAnchor & dp::Bottom) + resultY = h - offset.y; + else + resultY += offset.y; + + return GuiPosition(m2::PointF(resultX, resultY), m_elementAnchor); +} + +void PositionResolver::SetAnchorVertical(dp::Anchor anchor) +{ + m_elementAnchor = MergeAnchors(m_elementAnchor, anchor); +} + +void PositionResolver::SetAnchorHorizontal(dp::Anchor anchor) +{ + m_elementAnchor = MergeAnchors(m_elementAnchor, anchor); +} + +void PositionResolver::SetRelativeVertical(dp::Anchor anchor) +{ + m_resolveAnchor = MergeAnchors(m_resolveAnchor, anchor); +} + +void PositionResolver::SetRelativeHorizontal(dp::Anchor anchor) +{ + m_resolveAnchor = MergeAnchors(m_resolveAnchor, anchor); +} + +void PositionResolver::SetOffsetX(float x) +{ + m_offset.x = x; +} + +void PositionResolver::SetOffsetY(float y) +{ + m_offset.y = y; +} + +GuiSkin::GuiSkin(ReaderPtr const & reader) +{ + SkinLoader loader(m_resolvers); + ReaderSource > source(reader); + if (!ParseXML(source, loader)) + LOG(LERROR, ("Error parsing gui skin")); +} + +GuiPosition GuiSkin::ResolvePosition(GuiElement name) +{ + TResolversPair const & resolvers = m_resolvers[name]; + PositionResolver const & resolver = (m_displayWidth < m_displayHeight) ? resolvers.first : resolvers.second; + return resolver.Resolve(m_displayWidth, m_displayHeight); +} + +void GuiSkin::Resize(int w, int h) +{ + m_displayWidth = w; + m_displayHeight = h; +} + +ReaderPtr ResolveGuiSkinFile(string const & deviceType) +{ + Platform & pl = GetPlatform(); + ReaderPtr reader; + try + { + reader = pl.GetReader("resource-default/" + deviceType + ".ui"); + } + catch(FileAbsentException & e) + { + LOG(LINFO, ("Gui skin for : ", deviceType ,"not found")); + } + + if (reader.GetPtr() == 0) + { + try + { + reader = pl.GetReader("resource-default/default.ui"); + } + catch(FileAbsentException & e) + { + LOG(LINFO, ("Default gui skin not found")); + throw e; + } + } + + return reader; +} + +} diff --git a/drape_frontend/gui_skin.hpp b/drape_frontend/gui_skin.hpp new file mode 100644 index 0000000000..fe8d32b827 --- /dev/null +++ b/drape_frontend/gui_skin.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "../coding/reader.hpp" + +#include "../drape/drape_global.hpp" +#include "../geometry/point2d.hpp" + +#include "../std/map.hpp" + +namespace df +{ + +struct GuiPosition +{ + GuiPosition(m2::PointF const & pt, dp::Anchor anchor) + : m_pixelPivot(pt) + , m_anchor(anchor) {} + + m2::PointF const m_pixelPivot; + dp::Anchor const m_anchor; +}; + +class PositionResolver +{ +public: + PositionResolver() = default; + + GuiPosition Resolve(int w, int h) const; + void SetAnchorVertical(dp::Anchor anchor); + void SetAnchorHorizontal(dp::Anchor anchor); + void SetRelativeVertical(dp::Anchor anchor); + void SetRelativeHorizontal(dp::Anchor anchor); + void SetOffsetX(float x); + void SetOffsetY(float y); + +private: + dp::Anchor m_elementAnchor = dp::Center; + dp::Anchor m_resolveAnchor = dp::Center; + m2::PointF m_offset = m2::PointF::Zero(); +}; + +class GuiSkin +{ +public: + enum class GuiElement + { + CountryStatus, + Ruler, + Compass, + Copyright + }; + + GuiSkin(ReaderPtr const & reader); + + GuiPosition ResolvePosition(GuiElement name); + void Resize(int w, int h); + +private: + /// TResolversPair.first - Portrait (when weight < height) + /// TResolversPair.second - Landscape (when weight >= height) + typedef pair TResolversPair; + map m_resolvers; + + int m_displayWidth; + int m_displayHeight; +}; + +ReaderPtr ResolveGuiSkinFile(string const & deviceType); + +}