// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- /* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by: Marco Trevisan */ #include #include #include "DecorationsWidgets.h" namespace unity { namespace decoration { namespace { DECLARE_LOGGER(logger, "unity.decoration.widgets"); CompositeScreen* cscreen_ = CompositeScreen::get(screen); template constexpr T max(T a, T b) { return (a > b) ? a : b; } template constexpr T min(T a, T b) { return (a < b) ? a : b; } constexpr int clamp_size(int v) { return min(max(0, v), std::numeric_limits::max()); } } Item::Item() : visible(true) , focused(false) , sensitive(true) , mouse_owner(false) , scale(1.0f) , max_(std::numeric_limits::max(), std::numeric_limits::max()) { auto parent_relayout_cb = sigc::mem_fun(this, &Item::RequestRelayout); visible.changed.connect(sigc::hide(parent_relayout_cb)); geo_parameters_changed.connect(parent_relayout_cb); } void Item::SetSize(int width, int height) { natural_.width = clamp_size(width); natural_.height = clamp_size(height); SetMinWidth(width); SetMaxWidth(width); SetMinHeight(height); SetMaxHeight(height); } void Item::SetCoords(int x, int y) { auto& geo = InternalGeo(); if (geo.x() == x && geo.y() == y) return; geo.setX(x); geo.setY(y); geo_parameters_changed.emit(); } int Item::GetNaturalWidth() const { return natural_.width; } int Item::GetNaturalHeight() const { return natural_.height; } int Item::GetMaxWidth() const { return max_.width; }; int Item::GetMaxHeight() const { return max_.height; }; int Item::GetMinWidth() const { return min_.width; }; int Item::GetMinHeight() const { return min_.height; }; void Item::SetMaxWidth(int value) { int clamped = clamp_size(value); if (max_.width == clamped) return; max_.width = clamped; min_.width = min(min_.width, max_.width); if (Geometry().width() > max_.width) InternalGeo().setWidth(min(GetNaturalWidth(), max_.width)); geo_parameters_changed.emit(); } void Item::SetMinWidth(int value) { int clamped = clamp_size(value); if (min_.width == clamped) return; min_.width = clamped; max_.width = max(min_.width, max_.width); if (Geometry().width() < min_.width) InternalGeo().setWidth(min_.width); geo_parameters_changed.emit(); } void Item::SetMaxHeight(int value) { int clamped = clamp_size(value); if (max_.height == clamped) return; max_.height = clamped; min_.height = min(min_.height, max_.height); if (Geometry().height() > max_.height) InternalGeo().setHeight(min(GetNaturalHeight(), max_.height)); geo_parameters_changed.emit(); } void Item::SetMinHeight(int value) { int clamped = clamp_size(value); if (min_.height == clamped) return; min_.height = clamped; max_.height = max(min_.height, max_.height); if (Geometry().height() < min_.height) InternalGeo().setHeight(min_.height); geo_parameters_changed.emit(); } void Item::Damage() { cscreen_->damageRegion(Geometry()); } CompRect const& Item::Geometry() const { return const_cast(this)->InternalGeo(); } void Item::SetParent(BasicContainer::Ptr const& parent) { if (parent && parent_) { LOG_ERROR(logger) << "This item has already a parent!"; return; } parent_ = parent; } BasicContainer::Ptr Item::GetParent() const { return parent_; } BasicContainer::Ptr Item::GetTopParent() const { BasicContainer::Ptr parent = parent_; while (parent && parent->parent_) parent = parent->parent_; return parent; } void Item::RequestRelayout() { if (BasicContainer::Ptr const& parent = parent_.lock()) parent->Relayout(); } void Item::AddProperties(debug::IntrospectionData& data) { data.add(Geometry()) .add("max_size", max_) .add("min_size", min_) .add("natural_size", nux::Size(GetNaturalWidth(), GetNaturalHeight())) .add("visible", visible()) .add("focused", focused()) .add("sensitive", sensitive()) .add("mouse_owner", mouse_owner()) .add("is_container", IsContainer()); } // TexturedItem::TexturedItem() : dirty_region_(false) { geo_parameters_changed.connect([this] { dirty_region_ = true; }); } void TexturedItem::SetTexture(cu::SimpleTexture::Ptr const& tex) { auto prev_geo = Geometry(); if (!texture_.SetTexture(tex)) return; auto const& actual_geo = Geometry(); if (prev_geo != actual_geo) { max_ = { actual_geo.width(), actual_geo.height() }; min_ = max_; geo_parameters_changed.emit(); if (!actual_geo.contains(prev_geo)) cscreen_->damageRegion(prev_geo); } Damage(); } void TexturedItem::Draw(GLWindow* ctx, GLMatrix const& transformation, GLWindowPaintAttrib const& attrib, CompRegion const& clip, unsigned mask) { if (!visible || Geometry().isEmpty() || !texture_) return; if (dirty_region_) { texture_.quad.region = texture_.quad.box; dirty_region_ = false; } ctx->vertexBuffer()->begin(); ctx->glAddGeometry(texture_.quad.matrices, texture_.quad.region, clip); if (ctx->vertexBuffer()->end()) ctx->glDrawTexture(texture_, transformation, attrib, mask); } int TexturedItem::GetNaturalWidth() const { return (texture_) ? texture_.st->width() : Item::GetNaturalWidth(); } int TexturedItem::GetNaturalHeight() const { return (texture_) ? texture_.st->height() : Item::GetNaturalHeight(); } CompRect& TexturedItem::InternalGeo() { return texture_.quad.box; } void TexturedItem::SetCoords(int x, int y) { if (texture_.SetCoords(x, y)) geo_parameters_changed.emit(); } // BasicContainer::BasicContainer() : relayouting_(false) { geo_parameters_changed.connect(sigc::mem_fun(this, &BasicContainer::Relayout)); focused.changed.connect([this] (bool focused) { for (auto const& item : items_) if (item) item->focused = focused; }); scale.changed.connect([this] (float scale) { for (auto const& item : items_) if (item) item->scale = scale; }); } void BasicContainer::Relayout() { if (relayouting_) return; auto old_geo = Geometry(); relayouting_ = true; DoRelayout(); relayouting_ = false; if (old_geo != Geometry()) RequestRelayout(); } CompRect BasicContainer::ContentGeometry() const { return Geometry(); } void BasicContainer::AddProperties(debug::IntrospectionData& data) { Item::AddProperties(data); data.add(ContentGeometry()); } debug::Introspectable::IntrospectableList BasicContainer::GetIntrospectableChildren() { IntrospectableList children; for (auto const& item : items_) children.push_back(item.get()); return children; } // Layout::Layout() : inner_padding(0, sigc::mem_fun(this, &Layout::SetPadding)) , left_padding(0, sigc::mem_fun(this, &Layout::SetPadding)) , right_padding(0, sigc::mem_fun(this, &Layout::SetPadding)) , top_padding(0, sigc::mem_fun(this, &Layout::SetPadding)) , bottom_padding(0, sigc::mem_fun(this, &Layout::SetPadding)) {} void Layout::Append(Item::Ptr const& item) { if (!item || std::find(items_.begin(), items_.end(), item) != items_.end()) return; if (item->GetParent()) { LOG_ERROR(logger) << "Impossible to add an item that has already a parent"; return; } items_.push_back(item); item->focused = focused(); item->scale = scale(); item->SetParent(shared_from_this()); Relayout(); } void Layout::Remove(Item::Ptr const& item) { auto it = std::find(items_.begin(), items_.end(), item); if (it == items_.end()) return; item->SetParent(nullptr); items_.erase(it); Relayout(); } CompRect Layout::ContentGeometry() const { float scale = this->scale(); int left_padding = this->left_padding().CP(scale); int right_padding = this->right_padding().CP(scale); int top_padding = this->top_padding().CP(scale); int bottom_padding = this->bottom_padding().CP(scale); return CompRect(rect_.x() + min(left_padding, rect_.width()), rect_.y() + min(top_padding, rect_.height()), clamp_size(rect_.width() - left_padding - right_padding), clamp_size(rect_.height() - top_padding - bottom_padding)); } void Layout::DoRelayout() { int loop = 0; float scale = this->scale(); int inner_padding = this->inner_padding().CP(scale); int left_padding = this->left_padding().CP(scale); int right_padding = this->right_padding().CP(scale); int top_padding = this->top_padding().CP(scale); int bottom_padding = this->bottom_padding().CP(scale); nux::Size available_space(clamp_size(max_.width - left_padding - right_padding), clamp_size(max_.height - top_padding - bottom_padding)); do { nux::Size content(min(left_padding, max_.width), 0); for (auto const& item : items_) { if (!item->visible()) continue; if (loop == 0) { item->SetMinWidth(item->GetNaturalWidth()); item->SetMaxWidth(available_space.width); item->SetMinHeight(min(available_space.height, item->GetNaturalHeight())); item->SetMaxHeight(available_space.height); } auto const& item_geo = item->Geometry(); content.height = max(content.height, item_geo.height()); item->SetX(rect_.x() + content.width); if (item_geo.width() > 0) content.width += item_geo.width() + inner_padding; } if (!items_.empty() && content.width > inner_padding) content.width -= inner_padding; int actual_right_padding = max(0, min(right_padding, max_.width - content.width)); int vertical_padding = top_padding + bottom_padding; content.width += actual_right_padding; content.height += min(vertical_padding, max_.height); if (content.width < min_.width) content.width = min_.width; if (content.height < min_.height) content.height = min_.height; int exceeding_width = content.width - max_.width + inner_padding + right_padding - actual_right_padding; int content_y = rect_.y() + top_padding; for (auto it = items_.rbegin(); it != items_.rend(); ++it) { auto const& item = *it; if (!item->visible()) continue; auto const& item_geo = item->Geometry(); if (exceeding_width > 0) exceeding_width -= inner_padding; if (exceeding_width > 0 && item_geo.width() > 0) { int old_width = item_geo.width(); int max_item_width = clamp_size(old_width - exceeding_width); item->SetMaxWidth(max_item_width); exceeding_width -= (old_width - max_item_width); } item->SetY(content_y + (content.height - vertical_padding - item_geo.height()) / 2); } rect_.setWidth(content.width); rect_.setHeight(content.height); if (loop > 1) { LOG_ERROR(logger) << "Relayouting is taking more than expected, process should be completed in maximum two loops!"; break; } ++loop; } while (rect_.width() > max_.width || rect_.height() > max_.height); } void Layout::Draw(GLWindow* ctx, GLMatrix const& transformation, GLWindowPaintAttrib const& attrib, CompRegion const& clip, unsigned mask) { for (auto const& item : items_) { if (item->visible()) item->Draw(ctx, transformation, attrib, clip, mask); } } bool Layout::SetPadding(RawPixel& target, RawPixel const& new_value) { int padding = clamp_size(new_value); if (padding == target) return false; target = padding; Relayout(); return true; } void Layout::AddProperties(debug::IntrospectionData& data) { Item::AddProperties(data); data.add("inner_padding", inner_padding()) .add("left_padding", left_padding()) .add("right_padding", right_padding()) .add("top_padding", top_padding()) .add("bottom_padding", bottom_padding()); } } // decoration namespace } // unity namespace