| /* |
| * Copyright (C) 2007 Apple Inc. |
| * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
| * Copyright (C) 2008 Collabora Ltd. |
| * Copyright (C) 2008 INdT - Instituto Nokia de Tecnologia |
| * Copyright (C) 2009-2010 ProFUSION embedded systems |
| * Copyright (C) 2009-2011 Samsung Electronics |
| * Copyright (c) 2012 Intel Corporation. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "RenderThemeEfl.h" |
| |
| #include "CSSValueKeywords.h" |
| #include "CairoUtilitiesEfl.h" |
| #include "ExceptionCodePlaceholder.h" |
| #include "FloatRoundedRect.h" |
| #include "FontDescription.h" |
| #include "GraphicsContext.h" |
| #include "HTMLInputElement.h" |
| #include "InputTypeNames.h" |
| #include "NotImplemented.h" |
| #include "Page.h" |
| #include "PaintInfo.h" |
| #include "PlatformContextCairo.h" |
| #include "RenderBox.h" |
| #include "RenderObject.h" |
| #include "RenderProgress.h" |
| #include "RenderSlider.h" |
| #include "ScrollbarThemeEfl.h" |
| #include "Settings.h" |
| #include "UserAgentScripts.h" |
| #include "UserAgentStyleSheets.h" |
| #include <Ecore_Evas.h> |
| #include <Edje.h> |
| #include <new> |
| #include <wtf/text/CString.h> |
| #include <wtf/text/StringBuilder.h> |
| #include <wtf/text/WTFString.h> |
| |
| namespace WebCore { |
| |
| // TODO: change from object count to ecore_evas size (bytes) |
| // TODO: as objects are webpage/user defined and they can be very large. |
| #define RENDER_THEME_EFL_PART_CACHE_MAX 32 |
| |
| // Initialize default font size. |
| float RenderThemeEfl::defaultFontSize = 16.0f; |
| |
| static const float minCancelButtonSize = 5; |
| static const float maxCancelButtonSize = 21; |
| |
| static const float minSearchDecorationButtonSize = 1; |
| static const float maxSearchDecorationButtonSize = 15; |
| static const float searchFieldDecorationButtonOffset = 3; |
| |
| // Constants for progress tag animation. |
| // These values have been copied from RenderThemeGtk.cpp |
| static const int progressAnimationFrames = 10; |
| static const double progressAnimationInterval = 0.125; |
| |
| static const int sliderThumbWidth = 29; |
| static const int sliderThumbHeight = 11; |
| |
| #define _ASSERT_ON_RELEASE_RETURN(o, fmt, ...) \ |
| do { \ |
| if (!o) { \ |
| EINA_LOG_CRIT(fmt, ## __VA_ARGS__); \ |
| ASSERT(o); \ |
| return; \ |
| } \ |
| } while (0) |
| |
| #define _ASSERT_ON_RELEASE_RETURN_VAL(o, val, fmt, ...) \ |
| do { \ |
| if (!o) { \ |
| EINA_LOG_CRIT(fmt, ## __VA_ARGS__); \ |
| ASSERT(o); \ |
| return val; \ |
| } \ |
| } while (0) |
| |
| static const char* toEdjeGroup(FormType type) |
| { |
| static const char* groups[] = { |
| "webkit/widget/button", |
| "webkit/widget/radio", |
| "webkit/widget/entry", |
| "webkit/widget/checkbox", |
| "webkit/widget/combo", |
| "webkit/widget/progressbar", |
| "webkit/widget/scrollbar/horizontal_thumb", |
| "webkit/widget/scrollbar/horizontal_background", |
| "webkit/widget/scrollbar/vertical_thumb", |
| "webkit/widget/scrollbar/vertical_background", |
| "webkit/widget/search/field", |
| "webkit/widget/search/results_button", |
| "webkit/widget/search/results_decoration", |
| "webkit/widget/search/cancel_button", |
| "webkit/widget/slider/vertical", |
| "webkit/widget/slider/horizontal", |
| "webkit/widget/slider/thumb_vertical", |
| "webkit/widget/slider/thumb_horizontal", |
| "webkit/widget/spinner", |
| 0 |
| }; |
| ASSERT(type >= 0); |
| ASSERT((size_t)type < sizeof(groups) / sizeof(groups[0])); // Out of sync? |
| return groups[type]; |
| } |
| |
| static bool setSourceGroupForEdjeObject(Evas_Object* o, const String& themePath, const char* group) |
| { |
| ASSERT(o); |
| ASSERT(!themePath.isEmpty()); |
| |
| if (!edje_object_file_set(o, themePath.utf8().data(), group)) { |
| const char* message = edje_load_error_str(edje_object_load_error_get(o)); |
| EINA_LOG_ERR("Could not set theme group '%s' of file '%s': %s", group, themePath.utf8().data(), message); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void RenderThemeEfl::adjustSizeConstraints(RenderStyle& style, FormType type) const |
| { |
| loadThemeIfNeeded(); |
| |
| // These are always valid, even if no theme could be loaded. |
| const ThemePartDesc* desc = m_partDescs + (size_t)type; |
| |
| if (style.minWidth().isIntrinsic()) |
| style.setMinWidth(desc->min.width()); |
| if (style.minHeight().isIntrinsic()) |
| style.setMinHeight(desc->min.height()); |
| |
| if (desc->max.width().value() > 0 && style.maxWidth().isIntrinsicOrAuto()) |
| style.setMaxWidth(desc->max.width()); |
| if (desc->max.height().value() > 0 && style.maxHeight().isIntrinsicOrAuto()) |
| style.setMaxHeight(desc->max.height()); |
| |
| style.setPaddingTop(desc->padding.top()); |
| style.setPaddingBottom(desc->padding.bottom()); |
| style.setPaddingLeft(desc->padding.left()); |
| style.setPaddingRight(desc->padding.right()); |
| } |
| |
| static bool isFormElementTooLargeToDisplay(const IntSize& elementSize) |
| { |
| // This limit of 20000 pixels is hardcoded inside edje -- anything above this size |
| // will be clipped. This value seems to be reasonable enough so that hardcoding it |
| // here won't be a problem. |
| static const int maxEdjeDimension = 20000; |
| |
| return elementSize.width() > maxEdjeDimension || elementSize.height() > maxEdjeDimension; |
| } |
| |
| std::unique_ptr<RenderThemeEfl::ThemePartCacheEntry> RenderThemeEfl::ThemePartCacheEntry::create(const String& themePath, FormType type, const IntSize& size) |
| { |
| ASSERT(!themePath.isEmpty()); |
| |
| if (isFormElementTooLargeToDisplay(size) || size.isEmpty()) { |
| EINA_LOG_ERR("Cannot render an element of size %dx%d.", size.width(), size.height()); |
| return nullptr; |
| } |
| |
| auto entry = std::make_unique<ThemePartCacheEntry>(); |
| |
| entry->m_canvas = EflUniquePtr<Ecore_Evas>(ecore_evas_buffer_new(size.width(), size.height())); |
| if (!entry->canvas()) { |
| EINA_LOG_ERR("ecore_evas_buffer_new(%d, %d) failed.", size.width(), size.height()); |
| return nullptr; |
| } |
| |
| // By default EFL creates buffers without alpha. |
| ecore_evas_alpha_set(entry->canvas(), EINA_TRUE); |
| |
| entry->m_edje = EflUniquePtr<Evas_Object>(edje_object_add(ecore_evas_get(entry->canvas()))); |
| ASSERT(entry->edje()); |
| |
| if (!setSourceGroupForEdjeObject(entry->edje(), themePath, toEdjeGroup(type))) |
| return nullptr; |
| |
| entry->m_surface = createSurfaceForBackingStore(entry->canvas()); |
| if (!entry->surface()) |
| return nullptr; |
| |
| evas_object_resize(entry->edje(), size.width(), size.height()); |
| evas_object_show(entry->edje()); |
| |
| entry->type = type; |
| entry->size = size; |
| |
| return entry; |
| } |
| |
| void RenderThemeEfl::ThemePartCacheEntry::reuse(const String& themePath, FormType newType, const IntSize& newSize) |
| { |
| ASSERT(!themePath.isEmpty()); |
| |
| if (type != newType) { |
| type = newType; |
| if (!setSourceGroupForEdjeObject(edje(), themePath, toEdjeGroup(newType))) { |
| type = FormTypeLast; // Invalidate. |
| return; |
| } |
| } |
| |
| if (size != newSize) { |
| size = newSize; |
| ecore_evas_resize(canvas(), newSize.width(), newSize.height()); |
| evas_object_resize(edje(), newSize.width(), newSize.height()); |
| |
| m_surface = createSurfaceForBackingStore(canvas()); |
| if (!surface()) { |
| type = FormTypeLast; // Invalidate; |
| return; |
| } |
| } |
| } |
| |
| RenderThemeEfl::ThemePartCacheEntry* RenderThemeEfl::getThemePartFromCache(FormType type, const IntSize& size) |
| { |
| size_t reusableNodeIndex = 0; |
| |
| for (size_t i = 0; i < m_partCache.size(); ++i) { |
| ThemePartCacheEntry* candidatedEntry = m_partCache[i].get(); |
| if (candidatedEntry->size == size) { |
| if (candidatedEntry->type == type) { |
| // Found the right item, move it to the head of the list |
| // and return it. |
| auto temp = WTF::move(m_partCache[i]); |
| m_partCache.remove(i); |
| m_partCache.insert(0, WTF::move(temp)); |
| return m_partCache.first().get(); |
| } |
| reusableNodeIndex = i; |
| } |
| } |
| |
| if (m_partCache.size() < RENDER_THEME_EFL_PART_CACHE_MAX) { |
| auto entry = ThemePartCacheEntry::create(themePath(), type, size); |
| if (entry) |
| m_partCache.insert(0, WTF::move(entry)); |
| |
| return m_partCache.first().get(); |
| } |
| |
| // The cache is full, reuse the last item we found that had the |
| // requested size to avoid resizing. If there was none, reuse |
| // the last item of the list. |
| if (!reusableNodeIndex) |
| reusableNodeIndex = m_partCache.size() - 1; |
| |
| ThemePartCacheEntry* reusedEntry = m_partCache[reusableNodeIndex].get(); |
| ASSERT(reusedEntry); |
| reusedEntry->reuse(themePath(), type, size); |
| auto temp = WTF::move(m_partCache[reusableNodeIndex]); |
| m_partCache.remove(reusableNodeIndex); |
| m_partCache.insert(0, WTF::move(temp)); |
| |
| return m_partCache.first().get(); |
| } |
| |
| void RenderThemeEfl::clearThemePartCache() |
| { |
| for (auto& part : m_partCache) |
| part = nullptr; |
| } |
| |
| void RenderThemeEfl::applyEdjeStateFromForm(Evas_Object* object, const ControlStates* states, bool haveBackground) |
| { |
| const char* signals[] = { // keep in sync with WebCore/platform/ThemeTypes.h |
| "hovered", |
| "pressed", |
| "focused", |
| "enabled", |
| "checked", |
| "read-only", |
| "default", |
| "window-inactive", |
| "indeterminate", |
| "spinup" |
| }; |
| |
| edje_object_signal_emit(object, "reset", ""); |
| |
| for (size_t i = 0; i < WTF_ARRAY_LENGTH(signals); ++i) { |
| if (states->states() & (1 << i)) |
| edje_object_signal_emit(object, signals[i], ""); |
| } |
| |
| if (haveBackground) |
| edje_object_signal_emit(object, "styled", ""); |
| } |
| |
| void RenderThemeEfl::applyEdjeRTLState(Evas_Object* edje, const RenderObject& object, FormType type, const IntRect& rect) |
| { |
| if (type == SliderVertical || type == SliderHorizontal) { |
| if (!is<RenderSlider>(object)) |
| return; // probably have -webkit-appearance: slider.. |
| |
| HTMLInputElement& input = downcast<RenderSlider>(object).element(); |
| double valueRange = input.maximum() - input.minimum(); |
| |
| auto msg = std::make_unique<Edje_Message_Float_Set>(); |
| msg->count = 2; |
| |
| // The first parameter of the message decides if the progress bar |
| // grows from the end of the slider or from the beginning. On vertical |
| // sliders, it should always be the same and will not be affected by |
| // text direction settings. |
| if (object.style().direction() == RTL || type == SliderVertical) |
| msg->val[0] = 1; |
| else |
| msg->val[0] = 0; |
| |
| msg->val[1] = (input.valueAsNumber() - input.minimum()) / valueRange; |
| edje_object_message_send(edje, EDJE_MESSAGE_FLOAT_SET, 0, msg.get()); |
| } else if (type == ProgressBar) { |
| const auto& renderProgress = downcast<RenderProgress>(object); |
| |
| int max = rect.width(); |
| double value = renderProgress.position(); |
| |
| auto msg = std::make_unique<Edje_Message_Float_Set>(); |
| msg->count = 2; |
| |
| if (object.style().direction() == RTL) |
| msg->val[0] = (1.0 - value) * max; |
| else |
| msg->val[0] = 0; |
| msg->val[1] = value; |
| edje_object_message_send(edje, EDJE_MESSAGE_FLOAT_SET, 0, msg.get()); |
| } |
| } |
| |
| bool RenderThemeEfl::isControlStyled(const RenderStyle& style, const BorderData& border, const FillLayer& background, const Color& backgroundColor) const |
| { |
| return RenderTheme::isControlStyled(style, border, background, backgroundColor) || style.appearance() == MenulistButtonPart; |
| } |
| |
| bool RenderThemeEfl::paintThemePart(const RenderObject& object, FormType type, const PaintInfo& info, const IntRect& rect) |
| { |
| loadThemeIfNeeded(); |
| _ASSERT_ON_RELEASE_RETURN_VAL(edje(), false, "Could not paint native HTML part due to missing theme."); |
| |
| ThemePartCacheEntry* entry = getThemePartFromCache(type, rect.size()); |
| if (!entry) |
| return false; |
| |
| bool haveBackgroundColor = isControlStyled(object.style(), object.style().border(), *object.style().backgroundLayers(), Color::white); |
| ControlStates states(extractControlStatesForRenderer(object)); |
| applyEdjeStateFromForm(entry->edje(), &states, haveBackgroundColor); |
| |
| applyEdjeRTLState(entry->edje(), object, type, rect); |
| |
| edje_object_calc_force(entry->edje()); |
| edje_object_message_signal_process(entry->edje()); |
| evas_render(ecore_evas_get(entry->canvas())); |
| |
| cairo_t* cairo = info.context().platformContext()->cr(); |
| ASSERT(cairo); |
| |
| cairo_save(cairo); |
| cairo_set_source_surface(cairo, entry->surface(), rect.x(), rect.y()); |
| cairo_paint_with_alpha(cairo, 1.0); |
| cairo_restore(cairo); |
| |
| return false; |
| } |
| |
| bool RenderThemeEfl::paintThemePart(const GraphicsContext& context, FormType type, const IntRect& rect) |
| { |
| loadThemeIfNeeded(); |
| _ASSERT_ON_RELEASE_RETURN_VAL(edje(), false, "Could not paint native HTML part due to missing theme."); |
| |
| ThemePartCacheEntry* entry = getThemePartFromCache(type, rect.size()); |
| ASSERT(entry); |
| |
| edje_object_calc_force(entry->edje()); |
| edje_object_message_signal_process(entry->edje()); |
| evas_render(ecore_evas_get(entry->canvas())); |
| |
| cairo_t* cairo = context.platformContext()->cr(); |
| ASSERT(cairo); |
| |
| cairo_save(cairo); |
| cairo_set_source_surface(cairo, entry->surface(), rect.x(), rect.y()); |
| cairo_paint_with_alpha(cairo, 1.0); |
| cairo_restore(cairo); |
| |
| return false; |
| } |
| |
| PassRefPtr<RenderTheme> RenderThemeEfl::create(Page* page) |
| { |
| return adoptRef(new RenderThemeEfl(page)); |
| } |
| |
| PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page) |
| { |
| if (page) |
| return RenderThemeEfl::create(page); |
| |
| static RenderTheme* fallback = RenderThemeEfl::create(0).leakRef(); |
| return fallback; |
| } |
| |
| static void applyColorCallback(void* data, Evas_Object*, const char* /* signal */, const char* colorClass) |
| { |
| RenderThemeEfl* that = static_cast<RenderThemeEfl*>(data); |
| that->setColorFromThemeClass(colorClass); |
| that->platformColorsDidChange(); // Triggers relayout. |
| } |
| |
| static bool fillColorsFromEdjeClass(Evas_Object* o, const char* colorClass, Color* color1, Color* color2 = 0, Color* color3 = 0) |
| { |
| int r1, g1, b1, a1; |
| int r2, g2, b2, a2; |
| int r3, g3, b3, a3; |
| |
| if (!edje_object_color_class_get(o, colorClass, &r1, &g1, &b1, &a1, &r2, &g2, &b2, &a2, &r3, &g3, &b3, &a3)) |
| return false; |
| |
| if (color1) |
| color1->setRGB(makeRGBA(r1, g1, b1, a1)); |
| if (color2) |
| color2->setRGB(makeRGBA(r2, g2, b2, a2)); |
| if (color3) |
| color3->setRGB(makeRGBA(r3, g3, b3, a3)); |
| |
| return true; |
| } |
| |
| void RenderThemeEfl::setColorFromThemeClass(const char* colorClass) |
| { |
| ASSERT(edje()); |
| |
| if (!strcmp("webkit/selection/foreground", colorClass)) |
| m_supportsSelectionForegroundColor = fillColorsFromEdjeClass(edje(), colorClass, &m_activeSelectionForegroundColor, &m_inactiveSelectionForegroundColor); |
| else if (!strcmp("webkit/selection/background", colorClass)) |
| fillColorsFromEdjeClass(edje(), colorClass, &m_activeSelectionBackgroundColor, &m_inactiveSelectionBackgroundColor); |
| else if (!strcmp("webkit/focus_ring", colorClass)) { |
| if (!fillColorsFromEdjeClass(edje(), colorClass, &m_focusRingColor)) |
| return; |
| |
| // platformFocusRingColor() is only used for the default theme (without page) |
| // The following is ugly, but no other way to do it unless we change it to use page themes as much as possible. |
| RenderTheme::setCustomFocusRingColor(m_focusRingColor); |
| } |
| } |
| |
| void RenderThemeEfl::setThemePath(const String& newThemePath) |
| { |
| if (newThemePath == m_themePath) |
| return; |
| |
| if (newThemePath.isEmpty()) { |
| EINA_LOG_CRIT("No valid theme defined, things will not work properly."); |
| return; |
| } |
| |
| String oldThemePath = m_themePath; |
| m_themePath = newThemePath; |
| |
| // Keep the consistence by restoring the previous theme path |
| // if we cannot load the new one. |
| if (!loadTheme()) |
| m_themePath = oldThemePath; |
| } |
| |
| String RenderThemeEfl::themePath() const |
| { |
| #ifndef NDEBUG |
| if (edje()) { |
| const char* path; |
| edje_object_file_get(edje(), &path, 0); |
| ASSERT(m_themePath == path); |
| } |
| #endif |
| return m_themePath; |
| } |
| |
| bool RenderThemeEfl::loadTheme() |
| { |
| ASSERT(!m_themePath.isEmpty()); |
| |
| if (!canvas()) { |
| m_canvas = EflUniquePtr<Ecore_Evas>(ecore_evas_buffer_new(1, 1)); |
| _ASSERT_ON_RELEASE_RETURN_VAL(canvas(), false, |
| "Could not create canvas required by theme, things will not work properly."); |
| } |
| |
| EflUniquePtr<Evas_Object> o = EflUniquePtr<Evas_Object>(edje_object_add(ecore_evas_get(canvas()))); |
| _ASSERT_ON_RELEASE_RETURN_VAL(o, false, "Could not create new base Edje object."); |
| |
| if (!setSourceGroupForEdjeObject(o.get(), m_themePath, "webkit/base")) |
| return false; // Keep current theme. |
| |
| // Invalidate existing theme part cache. |
| if (edje()) |
| clearThemePartCache(); |
| |
| // Set new loaded theme, and apply it. |
| m_edje = WTF::move(o); |
| |
| const char* thickness = edje_object_data_get(m_edje.get(), "scrollbar.thickness"); |
| if (thickness && !Settings::mockScrollbarsEnabled()) |
| static_cast<ScrollbarThemeEfl&>(ScrollbarTheme::theme()).setScrollbarThickness(atoi(thickness)); |
| |
| edje_object_signal_callback_add(edje(), "color_class,set", "webkit/selection/foreground", applyColorCallback, this); |
| edje_object_signal_callback_add(edje(), "color_class,set", "webkit/selection/background", applyColorCallback, this); |
| edje_object_signal_callback_add(edje(), "color_class,set", "webkit/focus_ring", applyColorCallback, this); |
| |
| applyPartDescriptionsFrom(m_themePath); |
| |
| setColorFromThemeClass("webkit/selection/foreground"); |
| setColorFromThemeClass("webkit/selection/background"); |
| setColorFromThemeClass("webkit/focus_ring"); |
| |
| platformColorsDidChange(); // Schedules a relayout, do last. |
| |
| return true; |
| } |
| |
| void RenderThemeEfl::applyPartDescriptionFallback(ThemePartDesc* desc) |
| { |
| desc->min.setWidth(Length(0, Fixed)); |
| desc->min.setHeight(Length(0, Fixed)); |
| |
| desc->max.setWidth(Length(0, Fixed)); |
| desc->max.setHeight(Length(0, Fixed)); |
| |
| desc->padding = LengthBox(0, 0, 0, 0); |
| } |
| |
| void RenderThemeEfl::applyPartDescription(Evas_Object* object, ThemePartDesc* desc) |
| { |
| Evas_Coord minw, minh, maxw, maxh; |
| |
| edje_object_size_min_get(object, &minw, &minh); |
| if (!minw && !minh) |
| edje_object_size_min_calc(object, &minw, &minh); |
| |
| desc->min.setWidth(Length(minw, Fixed)); |
| desc->min.setHeight(Length(minh, Fixed)); |
| |
| edje_object_size_max_get(object, &maxw, &maxh); |
| desc->max.setWidth(Length(maxw, Fixed)); |
| desc->max.setHeight(Length(maxh, Fixed)); |
| |
| if (!edje_object_part_exists(object, "text_confinement")) |
| desc->padding = LengthBox(0, 0, 0, 0); |
| else { |
| Evas_Coord px, py, pw, ph; |
| Evas_Coord ox = 0, oy = 0, ow = 0, oh = 0; |
| int top, right, bottom, left; |
| |
| if (minw > 0) |
| ow = minw; |
| else |
| ow = 100; |
| if (minh > 0) |
| oh = minh; |
| else |
| oh = 100; |
| if (maxw > 0 && ow > maxw) |
| ow = maxw; |
| if (maxh > 0 && oh > maxh) |
| oh = maxh; |
| |
| evas_object_move(object, ox, oy); |
| evas_object_resize(object, ow, oh); |
| edje_object_calc_force(object); |
| edje_object_message_signal_process(object); |
| edje_object_part_geometry_get(object, "text_confinement", &px, &py, &pw, &ph); |
| |
| top = py - oy; |
| bottom = (oh + oy) - (ph + py); |
| |
| left = px - ox; |
| right = (ow + ox) - (pw + px); |
| |
| desc->padding = LengthBox(top, right, bottom, left); |
| } |
| } |
| |
| void RenderThemeEfl::applyPartDescriptionsFrom(const String& themePath) |
| { |
| EflUniquePtr<Evas_Object> temp = EflUniquePtr<Evas_Object>(edje_object_add(ecore_evas_get(canvas()))); |
| _ASSERT_ON_RELEASE_RETURN(temp, "Could not create Edje object."); |
| |
| for (size_t i = 0; i < FormTypeLast; i++) { |
| FormType type = static_cast<FormType>(i); |
| m_partDescs[i].type = type; |
| if (!setSourceGroupForEdjeObject(temp.get(), themePath, toEdjeGroup(type))) |
| applyPartDescriptionFallback(m_partDescs + i); |
| else |
| applyPartDescription(temp.get(), m_partDescs + i); |
| } |
| } |
| |
| RenderThemeEfl::RenderThemeEfl(Page* page) |
| : RenderTheme() |
| , m_page(page) |
| , m_activeSelectionBackgroundColor(0, 0, 255) |
| , m_activeSelectionForegroundColor(Color::white) |
| , m_inactiveSelectionBackgroundColor(0, 0, 128) |
| , m_inactiveSelectionForegroundColor(200, 200, 200) |
| , m_focusRingColor(32, 32, 224, 224) |
| , m_sliderThumbColor(Color::darkGray) |
| , m_supportsSelectionForegroundColor(false) |
| { |
| } |
| |
| RenderThemeEfl::~RenderThemeEfl() |
| { |
| clearThemePartCache(); |
| } |
| |
| static bool supportsFocus(ControlPart appearance) |
| { |
| switch (appearance) { |
| case PushButtonPart: |
| case ButtonPart: |
| case TextFieldPart: |
| case TextAreaPart: |
| case SearchFieldPart: |
| case MenulistPart: |
| case RadioPart: |
| case CheckboxPart: |
| case SliderVerticalPart: |
| case SliderHorizontalPart: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool RenderThemeEfl::supportsFocusRing(const RenderStyle& style) const |
| { |
| return supportsFocus(style.appearance()); |
| } |
| |
| bool RenderThemeEfl::controlSupportsTints(const RenderObject& object) const |
| { |
| return isEnabled(object); |
| } |
| |
| int RenderThemeEfl::baselinePosition(const RenderBox& box) const |
| { |
| if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart) |
| return box.marginTop() + box.height() - 3; |
| return RenderTheme::baselinePosition(box); |
| } |
| |
| Color RenderThemeEfl::platformActiveSelectionBackgroundColor() const |
| { |
| loadThemeIfNeeded(); |
| return m_activeSelectionBackgroundColor; |
| } |
| |
| Color RenderThemeEfl::platformInactiveSelectionBackgroundColor() const |
| { |
| loadThemeIfNeeded(); |
| return m_inactiveSelectionBackgroundColor; |
| } |
| |
| Color RenderThemeEfl::platformActiveSelectionForegroundColor() const |
| { |
| loadThemeIfNeeded(); |
| return m_activeSelectionForegroundColor; |
| } |
| |
| Color RenderThemeEfl::platformInactiveSelectionForegroundColor() const |
| { |
| loadThemeIfNeeded(); |
| return m_inactiveSelectionForegroundColor; |
| } |
| |
| Color RenderThemeEfl::platformFocusRingColor() const |
| { |
| loadThemeIfNeeded(); |
| return m_focusRingColor; |
| } |
| |
| bool RenderThemeEfl::supportsSelectionForegroundColors() const |
| { |
| loadThemeIfNeeded(); |
| return m_supportsSelectionForegroundColor; |
| } |
| |
| bool RenderThemeEfl::paintSliderTrack(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| if (object.style().appearance() == SliderHorizontalPart) |
| paintThemePart(object, SliderHorizontal, info, rect); |
| else |
| paintThemePart(object, SliderVertical, info, rect); |
| |
| #if ENABLE(DATALIST_ELEMENT) |
| paintSliderTicks(object, info, rect); |
| #endif |
| |
| return false; |
| } |
| |
| void RenderThemeEfl::adjustSliderTrackStyle(StyleResolver&, RenderStyle& style, Element*) const |
| { |
| style.setBoxShadow(nullptr); |
| } |
| |
| void RenderThemeEfl::adjustSliderThumbStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| RenderTheme::adjustSliderThumbStyle(styleResolver, style, element); |
| style.setBoxShadow(nullptr); |
| } |
| |
| void RenderThemeEfl::adjustSliderThumbSize(RenderStyle& style, Element*) const |
| { |
| ControlPart part = style.appearance(); |
| if (part == SliderThumbVerticalPart) { |
| style.setWidth(Length(sliderThumbHeight, Fixed)); |
| style.setHeight(Length(sliderThumbWidth, Fixed)); |
| } else if (part == SliderThumbHorizontalPart) { |
| style.setWidth(Length(sliderThumbWidth, Fixed)); |
| style.setHeight(Length(sliderThumbHeight, Fixed)); |
| } |
| } |
| |
| #if ENABLE(DATALIST_ELEMENT) |
| IntSize RenderThemeEfl::sliderTickSize() const |
| { |
| return IntSize(1, 6); |
| } |
| |
| int RenderThemeEfl::sliderTickOffsetFromTrackCenter() const |
| { |
| static const int sliderTickOffset = -12; |
| |
| return sliderTickOffset; |
| } |
| |
| LayoutUnit RenderThemeEfl::sliderTickSnappingThreshold() const |
| { |
| // The same threshold value as the Chromium port. |
| return 5; |
| } |
| #endif |
| |
| bool RenderThemeEfl::supportsDataListUI(const AtomicString& type) const |
| { |
| #if ENABLE(DATALIST_ELEMENT) |
| // FIXME: We need to support other types. |
| return type == InputTypeNames::email() |
| || type == InputTypeNames::range() |
| || type == InputTypeNames::search() |
| || type == InputTypeNames::url(); |
| #else |
| UNUSED_PARAM(type); |
| return false; |
| #endif |
| } |
| |
| bool RenderThemeEfl::paintSliderThumb(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| if (object.style().appearance() == SliderThumbHorizontalPart) |
| paintThemePart(object, SliderThumbHorizontal, info, rect); |
| else |
| paintThemePart(object, SliderThumbVertical, info, rect); |
| |
| return false; |
| } |
| |
| void RenderThemeEfl::adjustCheckboxStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| if (!m_page && element && element->document().page()) { |
| static_cast<RenderThemeEfl&>(element->document().page()->theme()).adjustCheckboxStyle(styleResolver, style, element); |
| return; |
| } |
| |
| adjustSizeConstraints(style, CheckBox); |
| |
| style.resetBorder(); |
| |
| const ThemePartDesc* desc = m_partDescs + (size_t)CheckBox; |
| if (style.width().value() < desc->min.width().value()) |
| style.setWidth(desc->min.width()); |
| if (style.height().value() < desc->min.height().value()) |
| style.setHeight(desc->min.height()); |
| } |
| |
| bool RenderThemeEfl::paintCheckbox(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| return paintThemePart(object, CheckBox, info, rect); |
| } |
| |
| void RenderThemeEfl::adjustRadioStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| if (!m_page && element && element->document().page()) { |
| static_cast<RenderThemeEfl&>(element->document().page()->theme()).adjustRadioStyle(styleResolver, style, element); |
| return; |
| } |
| |
| adjustSizeConstraints(style, RadioButton); |
| |
| style.resetBorder(); |
| |
| const ThemePartDesc* desc = m_partDescs + (size_t)RadioButton; |
| if (style.width().value() < desc->min.width().value()) |
| style.setWidth(desc->min.width()); |
| if (style.height().value() < desc->min.height().value()) |
| style.setHeight(desc->min.height()); |
| } |
| |
| bool RenderThemeEfl::paintRadio(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| return paintThemePart(object, RadioButton, info, rect); |
| } |
| |
| void RenderThemeEfl::adjustButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| if (!m_page && element && element->document().page()) { |
| static_cast<RenderThemeEfl&>(element->document().page()->theme()).adjustButtonStyle(styleResolver, style, element); |
| return; |
| } |
| |
| // adjustSizeConstrains can make SquareButtonPart's size wrong (by adjusting paddings), so call it only for PushButtonPart and ButtonPart |
| if (style.appearance() == PushButtonPart || style.appearance() == ButtonPart) |
| adjustSizeConstraints(style, Button); |
| } |
| |
| bool RenderThemeEfl::paintButton(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| return paintThemePart(object, Button, info, rect); |
| } |
| |
| void RenderThemeEfl::adjustMenuListStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| if (!m_page && element && element->document().page()) { |
| static_cast<RenderThemeEfl&>(element->document().page()->theme()).adjustMenuListStyle(styleResolver, style, element); |
| return; |
| } |
| adjustSizeConstraints(style, ComboBox); |
| style.resetBorder(); |
| style.setWhiteSpace(PRE); |
| |
| style.setLineHeight(RenderStyle::initialLineHeight()); |
| } |
| |
| bool RenderThemeEfl::paintMenuList(const RenderObject& object, const PaintInfo& info, const FloatRect& rect) |
| { |
| return paintThemePart(object, ComboBox, info, IntRect(rect)); |
| } |
| |
| void RenderThemeEfl::adjustMenuListButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| // Height is locked to auto if height is not specified. |
| style.setHeight(Length(Auto)); |
| |
| // The <select> box must be at least 12px high for the button to render the text inside the box without clipping. |
| const int dropDownBoxMinHeight = 12; |
| |
| // Calculate min-height of the <select> element. |
| int minHeight = style.fontMetrics().height(); |
| minHeight = std::max(minHeight, dropDownBoxMinHeight); |
| style.setMinHeight(Length(minHeight, Fixed)); |
| |
| adjustMenuListStyle(styleResolver, style, element); |
| } |
| |
| bool RenderThemeEfl::paintMenuListButtonDecorations(const RenderBox& object, const PaintInfo& info, const FloatRect& rect) |
| { |
| return paintMenuList(object, info, rect); |
| } |
| |
| void RenderThemeEfl::adjustTextFieldStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| if (!m_page && element && element->document().page()) { |
| static_cast<RenderThemeEfl&>(element->document().page()->theme()).adjustTextFieldStyle(styleResolver, style, element); |
| return; |
| } |
| adjustSizeConstraints(style, TextField); |
| style.resetBorder(); |
| } |
| |
| bool RenderThemeEfl::paintTextField(const RenderObject& object, const PaintInfo& info, const FloatRect& rect) |
| { |
| return paintThemePart(object, TextField, info, IntRect(rect)); |
| } |
| |
| void RenderThemeEfl::adjustTextAreaStyle(StyleResolver&, RenderStyle&, Element*) const |
| { |
| } |
| |
| bool RenderThemeEfl::paintTextArea(const RenderObject& object, const PaintInfo& info, const FloatRect& rect) |
| { |
| return paintTextField(object, info, rect); |
| } |
| |
| void RenderThemeEfl::adjustSearchFieldResultsButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| if (!m_page && element && element->document().page()) { |
| static_cast<RenderThemeEfl&>(element->document().page()->theme()).adjustSearchFieldResultsButtonStyle(styleResolver, style, element); |
| return; |
| } |
| adjustSizeConstraints(style, SearchFieldResultsButton); |
| style.resetBorder(); |
| style.setWhiteSpace(PRE); |
| |
| float fontScale = style.fontSize() / defaultFontSize; |
| int decorationSize = lroundf(std::min(std::max(minSearchDecorationButtonSize, defaultFontSize * fontScale), maxSearchDecorationButtonSize)); |
| |
| style.setWidth(Length(decorationSize + searchFieldDecorationButtonOffset, Fixed)); |
| style.setHeight(Length(decorationSize, Fixed)); |
| } |
| |
| bool RenderThemeEfl::paintSearchFieldResultsButton(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| return paintThemePart(object, SearchFieldResultsButton, info, rect); |
| } |
| |
| void RenderThemeEfl::adjustSearchFieldResultsDecorationPartStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| if (!m_page && element && element->document().page()) { |
| static_cast<RenderThemeEfl&>(element->document().page()->theme()).adjustSearchFieldResultsDecorationPartStyle(styleResolver, style, element); |
| return; |
| } |
| adjustSizeConstraints(style, SearchFieldResultsDecoration); |
| style.resetBorder(); |
| style.setWhiteSpace(PRE); |
| |
| float fontScale = style.fontSize() / defaultFontSize; |
| int decorationSize = lroundf(std::min(std::max(minSearchDecorationButtonSize, defaultFontSize * fontScale), maxSearchDecorationButtonSize)); |
| |
| style.setWidth(Length(decorationSize + searchFieldDecorationButtonOffset, Fixed)); |
| style.setHeight(Length(decorationSize, Fixed)); |
| } |
| |
| bool RenderThemeEfl::paintSearchFieldResultsDecorationPart(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| return paintThemePart(object, SearchFieldResultsDecoration, info, rect); |
| } |
| |
| void RenderThemeEfl::adjustSearchFieldCancelButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| if (!m_page && element && element->document().page()) { |
| static_cast<RenderThemeEfl&>(element->document().page()->theme()).adjustSearchFieldCancelButtonStyle(styleResolver, style, element); |
| return; |
| } |
| adjustSizeConstraints(style, SearchFieldCancelButton); |
| style.resetBorder(); |
| style.setWhiteSpace(PRE); |
| |
| // Logic taken from RenderThemeChromium.cpp. |
| // Scale the button size based on the font size. |
| float fontScale = style.fontSize() / defaultFontSize; |
| int cancelButtonSize = lroundf(std::min(std::max(minCancelButtonSize, defaultFontSize * fontScale), maxCancelButtonSize)); |
| |
| style.setWidth(Length(cancelButtonSize, Fixed)); |
| style.setHeight(Length(cancelButtonSize, Fixed)); |
| } |
| |
| bool RenderThemeEfl::paintSearchFieldCancelButton(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| return paintThemePart(object, SearchFieldCancelButton, info, rect); |
| } |
| |
| void RenderThemeEfl::adjustSearchFieldStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| if (!m_page && element && element->document().page()) { |
| static_cast<RenderThemeEfl&>(element->document().page()->theme()).adjustSearchFieldStyle(styleResolver, style, element); |
| return; |
| } |
| adjustSizeConstraints(style, SearchField); |
| style.resetBorder(); |
| style.setWhiteSpace(PRE); |
| } |
| |
| bool RenderThemeEfl::paintSearchField(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| return paintThemePart(object, SearchField, info, rect); |
| } |
| |
| void RenderThemeEfl::adjustInnerSpinButtonStyle(StyleResolver& styleResolver, RenderStyle& style, Element* element) const |
| { |
| if (!m_page && element && element->document().page()) { |
| static_cast<RenderThemeEfl&>(element->document().page()->theme()).adjustInnerSpinButtonStyle(styleResolver, style, element); |
| return; |
| } |
| adjustSizeConstraints(style, Spinner); |
| } |
| |
| bool RenderThemeEfl::paintInnerSpinButton(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| return paintThemePart(object, Spinner, info, rect); |
| } |
| |
| void RenderThemeEfl::setDefaultFontSize(int size) |
| { |
| defaultFontSize = size; |
| } |
| |
| void RenderThemeEfl::updateCachedSystemFontDescription(CSSValueID, FontCascadeDescription& fontDescription) const |
| { |
| // It was called by RenderEmbeddedObject::paintReplaced to render alternative string. |
| // To avoid cairo_error while rendering, fontDescription should be passed. |
| fontDescription.setOneFamily("Sans"); |
| fontDescription.setSpecifiedSize(defaultFontSize); |
| fontDescription.setIsAbsoluteSize(true); |
| fontDescription.setWeight(FontWeightNormal); |
| fontDescription.setItalic(FontItalicOff); |
| } |
| |
| void RenderThemeEfl::adjustProgressBarStyle(StyleResolver&, RenderStyle& style, Element*) const |
| { |
| style.setBoxShadow(nullptr); |
| } |
| |
| double RenderThemeEfl::animationRepeatIntervalForProgressBar(RenderProgress&) const |
| { |
| return progressAnimationInterval; |
| } |
| |
| double RenderThemeEfl::animationDurationForProgressBar(RenderProgress&) const |
| { |
| return progressAnimationInterval * progressAnimationFrames * 2; // "2" for back and forth; |
| } |
| |
| bool RenderThemeEfl::paintProgressBar(const RenderObject& object, const PaintInfo& info, const IntRect& rect) |
| { |
| if (!object.isProgress()) |
| return true; |
| |
| return paintThemePart(object, ProgressBar, info, rect); |
| } |
| |
| #if ENABLE(VIDEO) |
| String RenderThemeEfl::mediaControlsStyleSheet() |
| { |
| return ASCIILiteral(mediaControlsBaseUserAgentStyleSheet); |
| } |
| |
| String RenderThemeEfl::mediaControlsScript() |
| { |
| StringBuilder scriptBuilder; |
| scriptBuilder.append(mediaControlsLocalizedStringsJavaScript, sizeof(mediaControlsLocalizedStringsJavaScript)); |
| scriptBuilder.append(mediaControlsBaseJavaScript, sizeof(mediaControlsBaseJavaScript)); |
| return scriptBuilder.toString(); |
| } |
| #endif |
| |
| #undef _ASSERT_ON_RELEASE_RETURN |
| #undef _ASSERT_ON_RELEASE_RETURN_VAL |
| |
| } |