/*
* Copyright (C) 2011 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: Jason Smith
*/
#include "config.h"
#include "MultiMonitor.h"
#include "SwitcherView.h"
#include "unity-shared/AnimationUtils.h"
#include "unity-shared/IconRenderer.h"
#include "unity-shared/TimeUtil.h"
#include "unity-shared/UnitySettings.h"
#include "unity-shared/UScreen.h"
#include "unity-shared/XKeyboardUtil.h"
#include
namespace unity
{
using namespace ui;
using launcher::AbstractLauncherIcon;
namespace switcher
{
namespace
{
RawPixel const VERTICAL_PADDING = 45_em;
RawPixel const BORDER_SIZE = 50_em;
RawPixel const FLAT_SPACING = 20_em;
RawPixel const ICON_SIZE = 128_em;
RawPixel const MINIMUM_SPACING = 10_em;
RawPixel const TILE_SIZE = 150_em;
RawPixel const SPREAD_OFFSET = 100_em;
RawPixel const EXTRA_ICON_SPACE = 10_em;
RawPixel const TEXT_SIZE = 15_em;
RawPixel const LAYOUT_SPACING = 8_em;
RawPixel const LAYOUT_MAX_ROW_HEIGHT = 400_em;
unsigned int const ANIMATION_LENGTH = 250;
unsigned int const MAX_DIRECTIONS_CHANGED = 3;
unsigned int const SCROLL_WHEEL_EVENTS_DISTANCE = 75;
double const TEXT_TILE_MULTIPLIER = 3.5;
}
NUX_IMPLEMENT_OBJECT_TYPE(SwitcherView);
SwitcherView::SwitcherView(ui::AbstractIconRenderer::Ptr const& renderer)
: render_boxes(false)
, border_size(BORDER_SIZE.CP(scale))
, flat_spacing(FLAT_SPACING.CP(scale))
, icon_size(ICON_SIZE.CP(scale))
, minimum_spacing(MINIMUM_SPACING.CP(scale))
, tile_size(TILE_SIZE.CP(scale))
, vertical_size(tile_size + VERTICAL_PADDING.CP(scale) * 2)
, text_size(TEXT_SIZE.CP(scale))
, animation_length(ANIMATION_LENGTH)
, icon_renderer_(renderer)
, text_view_(new StaticCairoText(""))
, animation_(animation_length)
, last_icon_selected_(-1)
, last_detail_icon_selected_(-1)
, last_mouse_scroll_time_(0)
, check_mouse_first_time_(true)
, key_right_to_tab_(NUX_VK_q)
{
icon_renderer_->pip_style = OVER_TILE;
icon_renderer_->monitor = monitors::MAX;
icon_renderer_->SetTargetSize(tile_size, icon_size, minimum_spacing);
icon_renderer_->scale = scale();
text_view_->SetMaximumWidth(tile_size * TEXT_TILE_MULTIPLIER);
text_view_->SetLines(-1);
text_view_->SetTextColor(nux::color::White);
text_view_->SetFontSize(10);
text_view_->SetFontWeight(PANGO_WEIGHT_BOLD);
text_view_->SetScale(scale);
layout_system_.spacing = LAYOUT_SPACING.CP(scale);
layout_system_.max_row_height = LAYOUT_MAX_ROW_HEIGHT.CP(scale);
icon_size.changed.connect(sigc::mem_fun(this, &SwitcherView::OnIconSizeChanged));
tile_size.changed.connect(sigc::mem_fun(this, &SwitcherView::OnTileSizeChanged));
scale.changed.connect(sigc::mem_fun(this, &SwitcherView::OnScaleChanged));
mouse_move.connect (sigc::mem_fun(this, &SwitcherView::RecvMouseMove));
mouse_down.connect (sigc::mem_fun(this, &SwitcherView::RecvMouseDown));
mouse_up.connect (sigc::mem_fun(this, &SwitcherView::RecvMouseUp));
mouse_wheel.connect(sigc::mem_fun(this, &SwitcherView::RecvMouseWheel));
CaptureMouseDownAnyWhereElse(true);
SetAcceptMouseWheelEvent(true);
SetBackgroundHelperGeometryGetter([this] {
// XXX: remove me when switcher will have a proper BaseWindow
auto geo = GetAbsoluteGeometry();
geo.OffsetPosition(blur_geometry_.x, blur_geometry_.y);
geo.SetSize(blur_geometry_.width, blur_geometry_.height);
return geo;
});
if (Display* dpy = nux::GetGraphicsDisplay()->GetX11Display())
{
KeySym sym = keyboard::get_key_right_to_key_symbol(dpy, XStringToKeysym("Tab"));
if (sym != NoSymbol)
key_right_to_tab_ = sym;
}
animation_.updated.connect(sigc::hide(sigc::mem_fun(this, &SwitcherView::PreLayoutManagement)));
}
std::string SwitcherView::GetName() const
{
return "SwitcherView";
}
void SwitcherView::AddProperties(debug::IntrospectionData& introspection)
{
introspection
.add("render-boxes", render_boxes)
.add("border-size", border_size)
.add("flat-spacing", flat_spacing)
.add("icon-size", icon_size)
.add("minimum-spacing", minimum_spacing)
.add("tile-size", tile_size)
.add("vertical-size", vertical_size)
.add("text-size", text_size)
.add("animation-length", animation_length)
.add("spread-size", TEXT_TILE_MULTIPLIER)
.add("label", text_view_->GetText())
.add("last_icon_selected", last_icon_selected_)
.add("spread_offset", SPREAD_OFFSET.CP(scale))
.add("label_visible", text_view_->IsVisible());
}
debug::Introspectable::IntrospectableList SwitcherView::GetIntrospectableChildren()
{
std::list introspection_results;
if (model_->detail_selection)
{
for (auto const& target : render_targets_)
{
introspection_results.push_back(target.get());
}
}
else if (!last_args_.empty())
{
for (auto& args : last_args_)
{
introspection_results.push_back(&args);
}
}
return introspection_results;
}
LayoutWindow::Vector const& SwitcherView::ExternalTargets() const
{
return render_targets_;
}
void SwitcherView::SetModel(SwitcherModel::Ptr model)
{
model_ = model;
model->selection_changed.connect(sigc::mem_fun(this, &SwitcherView::OnSelectionChanged));
model->detail_selection.changed.connect (sigc::mem_fun (this, &SwitcherView::OnDetailSelectionChanged));
model->detail_selection_index.changed.connect (sigc::mem_fun (this, &SwitcherView::OnDetailSelectionIndexChanged));
model->updated.connect(sigc::mem_fun(this, &SwitcherView::QueueRelayout));
last_icon_selected_ = -1;
if (!model->Selection())
return;
text_view_->SetVisible(!model->detail_selection);
if (!model->detail_selection)
text_view_->SetText(model->Selection()->tooltip_text(), true);
}
void SwitcherView::OnIconSizeChanged(int size)
{
icon_renderer_->SetTargetSize(tile_size, icon_size, minimum_spacing);
}
void SwitcherView::OnTileSizeChanged(int size)
{
icon_renderer_->SetTargetSize(tile_size, icon_size, minimum_spacing);
vertical_size = tile_size + VERTICAL_PADDING.CP(scale) * 2;
}
void SwitcherView::OnScaleChanged(double scale)
{
text_view_->SetScale(scale);
border_size = BORDER_SIZE.CP(scale);
flat_spacing = FLAT_SPACING.CP(scale);
icon_size = ICON_SIZE.CP(scale);
minimum_spacing = MINIMUM_SPACING.CP(scale);
tile_size = TILE_SIZE.CP(scale);
text_size = TEXT_SIZE.CP(scale);
vertical_size = tile_size + VERTICAL_PADDING.CP(scale) * 2;
icon_renderer_->scale = scale;
layout_system_.spacing = LAYOUT_SPACING.CP(scale);
layout_system_.max_row_height = LAYOUT_MAX_ROW_HEIGHT.CP(scale);
}
void SwitcherView::StartAnimation()
{
animation_.SetDuration(Settings::Instance().low_gfx() ? 0 : animation_length);
animation::Start(animation_, animation::Direction::FORWARD);
}
void SwitcherView::SkipAnimation()
{
animation::Skip(animation_);
}
void SwitcherView::SaveLast()
{
saved_args_ = last_args_;
saved_background_ = last_background_;
StartAnimation();
}
void SwitcherView::OnDetailSelectionIndexChanged(unsigned int index)
{
QueueRelayout();
}
void SwitcherView::OnDetailSelectionChanged(bool detail)
{
text_view_->SetVisible(!detail);
last_detail_icon_selected_ = -1;
check_mouse_first_time_ = true;
if (!detail)
{
text_view_->SetText(model_->Selection()->tooltip_text(), true);
render_targets_.clear();
}
SaveLast();
}
void SwitcherView::OnSelectionChanged(AbstractLauncherIcon::Ptr const& selection)
{
if (selection)
text_view_->SetText(selection->tooltip_text(), true);
delta_tracker_.ResetState();
SaveLast();
}
nux::Point CalculateMouseMonitorOffset(int x, int y)
{
int monitor = unity::UScreen::GetDefault()->GetMonitorWithMouse();
nux::Geometry const& geo = unity::UScreen::GetDefault()->GetMonitorGeometry(monitor);
return {geo.x + x, geo.y + y};
}
void SwitcherView::MouseHandlingBackToNormal()
{
check_mouse_first_time_ = false;
last_icon_selected_ = -1;
last_detail_icon_selected_ = -1;
}
void SwitcherView::RecvMouseMove(int x, int y, int dx, int dy, unsigned long /*button_flags*/, unsigned long /*key_flags*/)
{
// We just started, and want to check if we are a bump or not.
// Once we are no longer a bump, skip!!
if (check_mouse_first_time_)
{
if (CheckMouseInsideBackground(x,y))
{
delta_tracker_.HandleNewMouseDelta(dx, dy);
if (delta_tracker_.AmountOfDirectionsChanged() >= MAX_DIRECTIONS_CHANGED)
{
MouseHandlingBackToNormal();
}
}
else
{
MouseHandlingBackToNormal();
}
}
if (model_->detail_selection)
{
HandleDetailMouseMove(x, y);
}
else
{
HandleMouseMove(x, y);
}
}
void SwitcherView::HandleDetailMouseMove(int x, int y)
{
nux::Point const& mouse_pos = CalculateMouseMonitorOffset(x, y);
int detail_icon_index = DetailIconIdexAt(mouse_pos.x, mouse_pos.y);
if (check_mouse_first_time_)
{
last_detail_icon_selected_ = detail_icon_index;
return;
}
if (detail_icon_index >= 0 && detail_icon_index != last_detail_icon_selected_)
{
model_->detail_selection_index = detail_icon_index;
last_detail_icon_selected_ = detail_icon_index;
}
else if (detail_icon_index < 0)
{
last_detail_icon_selected_ = -1;
}
}
void SwitcherView::HandleMouseMove(int x, int y)
{
int icon_index = IconIndexAt(x, y);
if (check_mouse_first_time_)
{
last_icon_selected_ = icon_index;
return;
}
if (icon_index >= 0)
{
if (icon_index != last_icon_selected_)
{
if (icon_index != model_->SelectionIndex())
{
model_->Select(icon_index);
}
last_icon_selected_ = icon_index;
}
switcher_mouse_move.emit(icon_index);
}
else if (icon_index < 0)
{
last_icon_selected_ = -1;
}
}
void SwitcherView::RecvMouseDown(int x, int y, unsigned long button_flags, unsigned long /*key_flags*/)
{
int button = nux::GetEventButton(button_flags);
if (!CheckMouseInsideBackground(x, y))
hide_request.emit(false);
if (model_->detail_selection)
{
HandleDetailMouseDown(x, y, button);
}
else
{
HandleMouseDown(x, y, button);
}
}
void SwitcherView::HandleDetailMouseDown(int x, int y, int button)
{
nux::Point const& mouse_pos = CalculateMouseMonitorOffset(x, y);
int detail_icon_index = DetailIconIdexAt(mouse_pos.x, mouse_pos.y);
last_detail_icon_selected_ = detail_icon_index;
}
void SwitcherView::HandleMouseDown(int x, int y, int button)
{
int icon_index = IconIndexAt(x,y);
last_icon_selected_ = icon_index;
}
void SwitcherView::RecvMouseUp(int x, int y, unsigned long button_flags, unsigned long /*key_flags*/)
{
int button = nux::GetEventButton(button_flags);
if (model_->detail_selection)
{
HandleDetailMouseUp(x, y, button);
}
else
{
HandleMouseUp(x, y, button);
}
}
void SwitcherView::HandleDetailMouseUp(int x, int y, int button)
{
nux::Point const& mouse_pos = CalculateMouseMonitorOffset(x, y);
int detail_icon_index = DetailIconIdexAt(mouse_pos.x, mouse_pos.y);
if (button == 1)
{
if (detail_icon_index >= 0 && detail_icon_index == last_detail_icon_selected_)
{
model_->detail_selection_index = detail_icon_index;
hide_request.emit(true);
}
else if (detail_icon_index < 0)
{
model_->detail_selection = false;
}
}
else if (button == 3)
{
model_->detail_selection = false;
}
}
void SwitcherView::HandleMouseUp(int x, int y, int button)
{
int icon_index = IconIndexAt(x,y);
if (button == 1 || button == 2 || button == 3)
{
if (icon_index >= 0 && icon_index == last_icon_selected_)
{
model_->Select(icon_index);
if (button == 1)
hide_request.emit(true);
else if (button == 2)
switcher_close_current.emit();
else if (button == 3)
switcher_start_detail.emit();
}
}
}
void SwitcherView::RecvMouseWheel(int /*x*/, int /*y*/, int wheel_delta, unsigned long /*button_flags*/, unsigned long /*key_flags*/)
{
auto timestamp = nux::GetGraphicsDisplay()->GetCurrentEvent().x11_timestamp;
if (timestamp - last_mouse_scroll_time_ <= SCROLL_WHEEL_EVENTS_DISTANCE)
return;
last_mouse_scroll_time_ = timestamp;
if (model_->detail_selection)
{
HandleDetailMouseWheel(wheel_delta);
}
else
{
HandleMouseWheel(wheel_delta);
}
}
void SwitcherView::HandleDetailMouseWheel(int wheel_delta)
{
if (wheel_delta < 0)
{
model_->NextDetail();
}
else
{
model_->PrevDetail();
}
}
void SwitcherView::HandleMouseWheel(int wheel_delta)
{
if (wheel_delta < 0)
{
model_->Next();
}
else
{
model_->Prev();
}
}
bool SwitcherView::InspectKeyEvent(unsigned int eventType, unsigned int keysym, const char* character)
{
if (eventType == nux::NUX_KEYDOWN)
{
switch(keysym)
{
case NUX_VK_UP:
switcher_stop_detail.emit();
break;
case NUX_VK_RIGHT:
switcher_next.emit();
break;
case NUX_VK_LEFT:
switcher_prev.emit();
break;
case NUX_VK_DOWN:
switcher_start_detail.emit();
break;
default:
if (keysym == key_right_to_tab_)
switcher_close_current.emit();
}
}
return true;
}
nux::Area* SwitcherView::FindKeyFocusArea(unsigned int key_symbol, unsigned long x11_key_code, unsigned long special_keys_state)
{
return this;
}
SwitcherModel::Ptr SwitcherView::GetModel()
{
return model_;
}
RenderArg SwitcherView::CreateBaseArgForIcon(AbstractLauncherIcon::Ptr const& icon)
{
RenderArg arg;
arg.icon = icon.GetPointer();
arg.alpha = 0.95f;
// tells the renderer to render arrows by number
arg.running_on_viewport = true;
arg.window_indicators = icon->WindowsVisibleOnMonitor(monitor);
if (arg.window_indicators > 1)
arg.running_arrow = true;
else
arg.window_indicators = 0;
if (icon == model_->Selection())
{
arg.keyboard_nav_hl = true;
arg.backlight_intensity = 1.0f;
}
else
{
arg.backlight_intensity = 0.7f;
}
if (icon->GetQuirk(AbstractLauncherIcon::Quirk::PROGRESS, monitor))
{
arg.progress_bias = 0.0;
arg.progress = CLAMP(icon->GetProgress(), 0.0f, 1.0f);
}
return arg;
}
RenderArg SwitcherView::InterpolateRenderArgs(RenderArg const& start, RenderArg const& end, float progress)
{
// easing
progress = -pow(progress - 1.0f, 2) + 1;
RenderArg result = end;
result.rotation.x = start.rotation.x + (end.rotation.x - start.rotation.x) * progress;
result.rotation.y = start.rotation.y + (end.rotation.y - start.rotation.y) * progress;
result.rotation.z = start.rotation.z + (end.rotation.z - start.rotation.z) * progress;
result.render_center.x = start.render_center.x + (end.render_center.x - start.render_center.x) * progress;
result.render_center.y = start.render_center.y + (end.render_center.y - start.render_center.y) * progress;
result.render_center.z = start.render_center.z + (end.render_center.z - start.render_center.z) * progress;
result.logical_center = result.render_center;
return result;
}
nux::Geometry SwitcherView::InterpolateBackground(nux::Geometry const& start, nux::Geometry const& end, float progress)
{
progress = -pow(progress - 1.0f, 2) + 1;
nux::Geometry result;
result.x = start.x + (end.x - start.x) * progress;
result.y = start.y + (end.y - start.y) * progress;
result.width = start.width + (end.width - start.width) * progress;
result.height = start.height + (end.height - start.height) * progress;
return result;
}
nux::Geometry SwitcherView::UpdateRenderTargets(float progress)
{
std::vector const& xids = model_->DetailXids();
render_targets_.clear();
for (Window window : xids)
{
bool selected = (window == model_->DetailSelectionWindow());
auto layout_window = std::make_shared(window);
layout_window->ComputeDecorationHeight();
layout_window->selected = selected;
layout_window->alpha = (selected ? 1.0f : 0.9f) * progress;
render_targets_.push_back(layout_window);
}
nux::Geometry max_bounds = GetAbsoluteGeometry();
nux::Size const& spread_size = SpreadSize();
max_bounds.x -= spread_size.width / 2;
max_bounds.y -= spread_size.height / 2;
max_bounds.width = spread_size.width;
max_bounds.height = spread_size.height;
nux::Geometry layout_geo;
layout_system_.LayoutWindows(render_targets_, max_bounds, layout_geo);
model_->SetRowSizes(layout_system_.GetRowSizes(render_targets_, max_bounds));
return layout_geo;
}
void SwitcherView::ResizeRenderTargets(nux::Geometry const& layout_geo, float progress)
{
if (progress >= 1.0f)
return;
// Animate the windows thumbnail sizes to make them grow with the switcher
float to_finish = 1.0f - progress;
nux::Point layout_abs_center((layout_geo.x + layout_geo.width/2.0f) * to_finish,
(layout_geo.y + layout_geo.height/2.0f) * to_finish);
for (LayoutWindow::Ptr const& win : render_targets_)
{
win->scale *= progress;
win->result = win->result * progress;
win->result.x += layout_abs_center.x;
win->result.y += layout_abs_center.y;
}
}
void SwitcherView::OffsetRenderTargets(int x, int y)
{
for (LayoutWindow::Ptr const& target : render_targets_)
{
target->result.x += x;
target->result.y += y;
}
}
nux::Size SwitcherView::SpreadSize()
{
nux::Geometry const& base = GetGeometry();
nux::Size result(base.width - border_size * 2, base.height - border_size * 2);
int width_padding = std::max(model_->Size() - 1, 0) * minimum_spacing + tile_size;
result.width -= width_padding;
return result;
}
void GetFlatIconPositions (int n_flat_icons,
int size,
int selection,
int &first_flat,
int &last_flat,
int &half_fold_left,
int &half_fold_right)
{
half_fold_left = -1;
half_fold_right = -1;
if (n_flat_icons == 0)
{
first_flat = selection + 1;
last_flat = selection;
}
else if (n_flat_icons == 1)
{
if (selection == 0)
{
// selection is first item
first_flat = 0;
last_flat = n_flat_icons;
}
else if (selection >= size - 2)
{
// selection is in ending area where all icons at end should flatten
first_flat = size - n_flat_icons - 1;
last_flat = size - 1;
}
else
{
first_flat = selection;
last_flat = selection;
half_fold_left = first_flat - 1;
half_fold_right = last_flat + 1;
}
}
else
{
if (selection == 0)
{
// selection is first item
first_flat = 0;
last_flat = n_flat_icons;
}
else if (selection >= 1 && selection <= n_flat_icons - 1)
{
// selection is in "beginning" area before flat section starts moving
// first item should half fold still
first_flat = 1;
last_flat = n_flat_icons;
half_fold_left = 0;
half_fold_right = last_flat + 1;
}
else if (selection >= size - 2)
{
// selection is in ending area where all icons at end should flatten
first_flat = size - n_flat_icons - 1;
last_flat = size - 1;
}
else
{
first_flat = selection - n_flat_icons + 2;
last_flat = selection + 1;
half_fold_left = first_flat - 1;
half_fold_right = last_flat + 1;
}
}
}
bool SwitcherView::RenderArgsFlat(nux::Geometry& background_geo, int selection, float progress)
{
bool any_changed = true;
last_args_.clear();
nux::Geometry const& base = GetGeometry();
background_geo.y = base.y + base.height / 2 - (vertical_size / 2);
background_geo.height = vertical_size;
if (text_view_->IsVisible())
{
background_geo.height += text_size;
text_view_->SetBaseY(background_geo.y + background_geo.height - VERTICAL_PADDING.CP(scale));
}
if (model_)
{
bool detail_selection = model_->detail_selection;
int size = model_->Size();
int padded_tile_size = tile_size + flat_spacing * 2;
int max_width = base.width - border_size * 2;
int spread_padded_width = 0;
if (detail_selection)
{
nux::Geometry const& spread_bounds = UpdateRenderTargets(progress);
ResizeRenderTargets(spread_bounds, progress);
// remove extra space consumed by spread
spread_padded_width = spread_bounds.width + SPREAD_OFFSET.CP(scale);
max_width -= spread_padded_width - tile_size;
int expansion = std::max(0, spread_bounds.height - icon_size);
background_geo.y -= expansion / 2;
background_geo.height += expansion;
}
int flat_width = size * padded_tile_size;
int n_flat_icons = CLAMP((max_width - size * minimum_spacing) / padded_tile_size - 1, 0, size);
int overflow = flat_width - max_width;
float x = 0;
if (overflow < 0)
{
background_geo.x = base.x - overflow / 2;
background_geo.width = base.width + overflow;
x -= overflow / 2;
overflow = 0;
}
else
{
background_geo.x = base.x;
background_geo.width = base.width;
}
int non_flat_icons = std::max (1.0f, (float)size - n_flat_icons - 1);
float partial_overflow = (float) overflow / (float)non_flat_icons;
float partial_overflow_scalar = (float)(padded_tile_size - partial_overflow) / (float)(padded_tile_size);
int first_flat, last_flat;
int half_fold_left;
int half_fold_right;
GetFlatIconPositions (n_flat_icons, size, selection, first_flat, last_flat, half_fold_left, half_fold_right);
int i = 0;
int y = base.y + base.height / 2;
x += border_size;
auto& results = last_args_;
for (auto const& icon : *model_)
{
RenderArg arg = CreateBaseArgForIcon(icon);
float scalar = partial_overflow_scalar;
bool should_flat = false;
if (i >= first_flat && i <= last_flat)
{
scalar = 1.0f;
should_flat = true;
}
else if (i == half_fold_left || i == half_fold_right)
{
scalar += (1.0f - scalar) * 0.5f;
}
int half_size = tile_size / 2;
if (i == selection && detail_selection)
{
half_size = spread_padded_width / 2;
scalar = 1.0f;
}
x += (half_size + flat_spacing) * scalar;
arg.render_center = nux::Point3(should_flat ? std::floor(x) : x, y, 0);
x += (half_size + flat_spacing) * scalar;
arg.rotation.y = (1.0f - std::min(1.0f, scalar));
if (!should_flat && overflow > 0 && i != selection)
{
if (i > last_flat)
{
arg.render_center.x -= 20;
}
else
{
arg.render_center.x += 20;
arg.rotation.y = -arg.rotation.y;
}
}
arg.render_center.z = abs(80.0f * arg.rotation.y);
arg.logical_center = arg.render_center;
if (i == selection && detail_selection)
{
arg.skip = true;
OffsetRenderTargets(arg.render_center.x, arg.render_center.y);
}
results.push_back(arg);
++i;
}
if (background_geo != blur_geometry_)
{
/* Update the blurred area geometry only if the final background is
* bigger than the previous blur geometry or if we've finished the
* animation; in this way we update the blurred area before growing
* and after that we've resized the view to the smaller size */
if ((background_geo.width >= blur_geometry_.width &&
background_geo.height >= blur_geometry_.height) || progress >= 1.0f)
{
blur_geometry_ = background_geo;
// Notify BackgroundEffectHelper
geometry_changed.emit(this, blur_geometry_);
}
}
bool result_size_changed = (saved_args_.size() != results.size());
any_changed = result_size_changed;
if (!result_size_changed && progress < 1.0f)
{
auto& end = results;
for (auto start_it = saved_args_.begin(), end_it = end.begin(); start_it != saved_args_.end(); ++start_it, ++end_it)
{
if (*start_it != *end_it)
{
any_changed = true;
if (start_it->render_center == end_it->render_center &&
start_it->rotation == end_it->rotation)
{
/* If a value that we don't animate has changed, we only care about
* redrawing the icons once, so we return true here, but we also
* update the saved RenderArg so that next time this function
* will be called, we don't consider it changed (unless reprocessed). */
*start_it = *end_it;
continue;
}
*end_it = InterpolateRenderArgs(*start_it, *end_it, progress);
}
}
background_geo = InterpolateBackground(saved_background_, background_geo, progress);
}
}
return any_changed;
}
void SwitcherView::PreLayoutManagement()
{
UnityWindowView::PreLayoutManagement();
double progress = animation_.GetCurrentValue();
nux::Geometry background_geo;
bool any_changed = RenderArgsFlat(background_geo, model_ ? model_->SelectionIndex() : 0, progress);
if (background_geo != last_background_ || any_changed)
{
last_background_ = background_geo;
QueueDraw();
}
}
void SwitcherView::PreDraw(nux::GraphicsEngine& GfxContext, bool force_draw)
{
icon_renderer_->PreprocessIcons(last_args_, GetGeometry());
}
nux::Geometry SwitcherView::GetBackgroundGeometry()
{
return last_background_;
}
nux::Geometry SwitcherView::GetBlurredBackgroundGeometry()
{
return blur_geometry_;
}
void SwitcherView::DrawOverlay(nux::GraphicsEngine& GfxContext, bool force_draw, nux::Geometry const& clip)
{
nux::Geometry const& base = GetGeometry();
nux::Geometry internal_clip = clip;
GfxContext.GetRenderStates().SetPremultipliedBlend(nux::SRC_OVER);
for (auto const& arg : last_args_)
{
if (text_view_->IsVisible() && model_->Selection() == arg.icon)
{
int view_width = text_view_->GetBaseWidth();
int start_x = arg.render_center.x - view_width / 2;
internal_clip.Expand (-10, -10);
if (start_x < internal_clip.x)
start_x = internal_clip.x;
else if (start_x + view_width > internal_clip.x + internal_clip.width)
start_x = (internal_clip.x + internal_clip.width) - view_width;
text_view_->SetBaseX(start_x);
}
if (arg.rotation.y < 0)
icon_renderer_->RenderIcon(GfxContext, arg, base, base);
}
for (auto rit = last_args_.rbegin(); rit != last_args_.rend(); ++rit)
{
if (rit->rotation.y >= 0)
icon_renderer_->RenderIcon(GfxContext, *rit, base, base);
}
if (render_boxes)
{
float val = 0.1f;
for (LayoutWindow::Ptr const& layout : render_targets_)
{
gPainter.Paint2DQuadColor(GfxContext, layout->result, nux::Color(val, val, val ,val));
val += 0.1f;
if (val > 1)
val = 0.1f;
}
for (auto rit = last_args_.rbegin(); rit != last_args_.rend(); ++rit)
{
nux::Geometry tmp (rit->render_center.x - 1, rit->render_center.y - 1, 2, 2);
gPainter.Paint2DQuadColor(GfxContext, tmp, nux::color::Red);
}
}
if (text_view_->IsVisible())
{
nux::GetPainter().PushPaintLayerStack();
text_view_->Draw(GfxContext, force_draw);
nux::GetPainter().PopPaintLayerStack();
}
}
int SwitcherView::IconIndexAt(int x, int y) const
{
int half_size = icon_size.Get() / 2 + EXTRA_ICON_SPACE.CP(scale);
int icon_index = -1;
// Taking icon rotation into consideration will make selection more
// accurate when there are many icons present and the user clicks/taps
// on icons close to edges. But manual testing has shown that the current
// implementation is enough.
int i = 0;
for (auto const& arg : last_args_)
{
if (x < (arg.logical_center.x - half_size)
|| x > (arg.logical_center.x + half_size))
{
++i;
}
else if (y < (arg.logical_center.y - half_size)
|| y > (arg.logical_center.y + half_size))
{
++i;
}
else
{
icon_index = i;
break;
}
}
return icon_index;
}
int SwitcherView::DetailIconIdexAt(int x, int y) const
{
int index = -1;
for (unsigned int i = 0; i < render_targets_.size(); ++i)
{
if (render_targets_[i]->result.IsPointInside(x + SPREAD_OFFSET.CP(scale), y + SPREAD_OFFSET.CP(scale)))
return i;
}
return index;
}
bool SwitcherView::CheckMouseInsideBackground(int x, int y) const
{
nux::Point p(x,y);
if (last_background_.IsInside(p))
return true;
return false;
}
}
}