/* * 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; } } }