// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- /* * Copyright (C) 2010 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: Jay Taoko * Authored by: Mirco Müller #include #include #include #include #include #include #include #include #include #include "unity-shared/CairoTexture.h" #include "QuicklistView.h" #include "QuicklistMenuItem.h" #include "QuicklistMenuItemLabel.h" #include "QuicklistMenuItemSeparator.h" #include "QuicklistMenuItemCheckmark.h" #include "QuicklistMenuItemRadio.h" #include "unity-shared/Introspectable.h" #include "unity-shared/PanelStyle.h" #include "unity-shared/DecorationStyle.h" #include "unity-shared/UnitySettings.h" #include "unity-shared/UScreen.h" #include "unity-shared/UBusWrapper.h" #include "unity-shared/UBusMessages.h" #include "unity-shared/DashStyle.h" namespace unity { namespace { const RawPixel ANCHOR_WIDTH = 10_em; const RawPixel TOP_SIZE = 4_em; const RawPixel ANCHOR_HEIGHT = 18_em; const RawPixel CORNER_RADIUS = 4_em; const RawPixel MAX_HEIGHT = 1000_em; const RawPixel MAX_WIDTH = 1000_em; const RawPixel LEFT_PADDING_CORRECTION = -1_em; const RawPixel OFFSET_CORRECTION = -1_em; } NUX_IMPLEMENT_OBJECT_TYPE(QuicklistView); QuicklistView::QuicklistView(int monitor) : CairoBaseWindow(monitor) , _anchorX(0) , _anchorY(0) , _labelText("QuicklistView 1234567890") , _top_size(TOP_SIZE) , _padding(decoration::Style::Get()->ActiveShadowRadius()) , _mouse_down(false) , _enable_quicklist_for_testing(false) , _restore_input_focus(false) , _cairo_text_has_changed(true) , _current_item_index(-1) { SetGeometry(nux::Geometry(0, 0, 1, 1)); int width = 0; int height = 0; // when launcher is on the left, the anchor is on the left of the menuitem, and // when launcher is on the bottom, the anchor is on the bottom of the menuitem. if (Settings::Instance().launcher_position == LauncherPosition::LEFT) width = ANCHOR_WIDTH; else height = ANCHOR_WIDTH; _left_space = new nux::SpaceLayout(RawPixel(_padding + width + CORNER_RADIUS + LEFT_PADDING_CORRECTION).CP(cv_), RawPixel(_padding + width + CORNER_RADIUS + LEFT_PADDING_CORRECTION).CP(cv_), 1, MAX_HEIGHT.CP(cv_)); _right_space = new nux::SpaceLayout(_padding.CP(cv_) + CORNER_RADIUS.CP(cv_), _padding.CP(cv_) + CORNER_RADIUS.CP(cv_), 1, MAX_HEIGHT.CP(cv_)); _top_space = new nux::SpaceLayout(1, MAX_WIDTH.CP(cv_), _padding.CP(cv_) + CORNER_RADIUS.CP(cv_), _padding.CP(cv_) + CORNER_RADIUS.CP(cv_)); _bottom_space = new nux::SpaceLayout(1, MAX_WIDTH.CP(cv_), _padding.CP(cv_) + height + CORNER_RADIUS.CP(cv_), _padding.CP(cv_) + height + CORNER_RADIUS.CP(cv_)); _vlayout = new nux::VLayout(TEXT(""), NUX_TRACKER_LOCATION); _vlayout->AddLayout(_top_space, 0); _item_layout = new nux::VLayout(TEXT(""), NUX_TRACKER_LOCATION); _vlayout->AddLayout(_item_layout, 0); _vlayout->AddLayout(_bottom_space, 0); _vlayout->SetMinimumWidth(RawPixel(140).CP(cv_)); _hlayout = new nux::HLayout(TEXT(""), NUX_TRACKER_LOCATION); _hlayout->AddLayout(_left_space, 0); _hlayout->AddLayout(_vlayout, 0, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); _hlayout->AddLayout(_right_space, 0); SetWindowSizeMatchLayout(true); SetLayout(_hlayout); mouse_down_outside_pointer_grab_area.connect(sigc::mem_fun(this, &QuicklistView::RecvMouseDownOutsideOfQuicklist)); mouse_down.connect(sigc::mem_fun(this, &QuicklistView::RecvMouseDown)); mouse_up.connect(sigc::mem_fun(this, &QuicklistView::RecvMouseUp)); mouse_click.connect(sigc::mem_fun(this, &QuicklistView::RecvMouseClick)); mouse_move.connect(sigc::mem_fun(this, &QuicklistView::RecvMouseMove)); mouse_drag.connect(sigc::mem_fun(this, &QuicklistView::RecvMouseDrag)); key_down.connect(sigc::mem_fun(this, &QuicklistView::RecvKeyPressed)); begin_key_focus.connect(sigc::mem_fun(this, &QuicklistView::RecvStartFocus)); end_key_focus.connect(sigc::mem_fun(this, &QuicklistView::RecvEndFocus)); SetAcceptKeyNavFocus(true); } int QuicklistView::CalculateX() const { int x = 0; if (Settings::Instance().launcher_position() == LauncherPosition::LEFT) x = _anchorX - _padding.CP(cv_); else { int size = 0; int max = GetBaseWidth() - ANCHOR_HEIGHT.CP(cv_) - 2 * CORNER_RADIUS.CP(cv_) - 2 * _padding.CP(cv_); if (_top_size.CP(cv_) > max) { size = max; } else if (_top_size.CP(cv_) > 0) { size = _top_size.CP(cv_); } x = _anchorX - (ANCHOR_HEIGHT.CP(cv_) / 2) - size - CORNER_RADIUS.CP(cv_) - _padding.CP(cv_); } return x; } int QuicklistView::CalculateY() const { int y = 0; if (Settings::Instance().launcher_position() == LauncherPosition::LEFT) y = _anchorY - (ANCHOR_HEIGHT.CP(cv_) / 2) - _top_size.CP(cv_) - CORNER_RADIUS.CP(cv_) - _padding.CP(cv_); else y = _anchorY - GetBaseHeight() + _padding.CP(cv_); return y; } void QuicklistView::RecvStartFocus() { PushToFront(); } void QuicklistView::RecvEndFocus() { } void QuicklistView::SelectItem(int index) { CancelItemsPrelightStatus(); int target_item = -1; if (IsMenuItemSelectable(index)) { QuicklistMenuItem* menu_item = GetNthItems(index); if (menu_item) { target_item = index; menu_item->Select(); } } if (_current_item_index != target_item) { _current_item_index = target_item; selection_change.emit(); QueueDraw(); } } bool QuicklistView::IsMenuItemSelectable(int index) { QuicklistMenuItem* menu_item = nullptr; if (index < 0) return false; menu_item = GetNthItems(index); if (!menu_item) return false; return menu_item->GetSelectable(); } void QuicklistView::RecvKeyPressed(unsigned long eventType, unsigned long key_sym, unsigned long key_state, const char* character, unsigned short keyCount) { switch (key_sym) { // home or page up (highlight the first menu-hitem) case NUX_VK_PAGE_UP: case NUX_VK_HOME: { int num_items = GetNumItems(); int target_index = -1; do { ++target_index; } while (!IsMenuItemSelectable(target_index) && target_index < num_items); if (target_index < num_items) SelectItem(target_index); break; } // end or page down (highlight the last menu-hitem) case NUX_VK_PAGE_DOWN: case NUX_VK_END: { int target_index = GetNumItems(); do { --target_index; } while (!IsMenuItemSelectable(target_index) && target_index >= 0); if (target_index >= 0) SelectItem(target_index); break; } // up (highlight previous menu-item) case NUX_VK_UP: case NUX_KP_UP: { int target_index = _current_item_index; bool loop_back = false; if (target_index <= 0) target_index = GetNumItems(); do { --target_index; // If the first item is not selectable, we must loop from the last one if (!loop_back && target_index == 0 && !IsMenuItemSelectable(target_index)) { loop_back = true; target_index = GetNumItems() - 1; } } while (!IsMenuItemSelectable(target_index) && target_index >= 0); if (target_index >= 0) SelectItem(target_index); break; } // down (highlight next menu-item) case NUX_VK_DOWN: case NUX_KP_DOWN: { int target_index = _current_item_index; int num_items = GetNumItems(); bool loop_back = false; if (target_index >= num_items - 1) target_index = -1; do { ++target_index; // If the last item is not selectable, we must loop from the first one if (!loop_back && target_index == num_items - 1 && !IsMenuItemSelectable(target_index)) { loop_back = true; target_index = 0; } } while (!IsMenuItemSelectable(target_index) && target_index < num_items); if (target_index < num_items) SelectItem(target_index); break; } // left (close quicklist, go back to laucher key-nav) case NUX_VK_LEFT: case NUX_KP_LEFT: if (Settings::Instance().launcher_position() == LauncherPosition::BOTTOM) { PromptHide(); UBusManager::SendMessage(UBUS_QUICKLIST_END_KEY_NAV); UBusManager::SendMessage(UBUS_LAUNCHER_PREV_KEY_NAV); UBusManager::SendMessage(UBUS_LAUNCHER_OPEN_QUICKLIST); } else { HideAndEndQuicklistNav(); } break; // right (close quicklist, go back to launcher key-nav) case NUX_VK_RIGHT: case NUX_KP_RIGHT: if (Settings::Instance().launcher_position() == LauncherPosition::BOTTOM) { PromptHide(); UBusManager::SendMessage(UBUS_QUICKLIST_END_KEY_NAV); UBusManager::SendMessage(UBUS_LAUNCHER_NEXT_KEY_NAV); UBusManager::SendMessage(UBUS_LAUNCHER_OPEN_QUICKLIST); } break; // esc (close quicklist, exit key-nav) case NUX_VK_ESCAPE: Hide(); // inform UnityScreen we leave key-nav completely UBusManager::SendMessage(UBUS_LAUNCHER_END_KEY_NAV, glib::Variant(_restore_input_focus)); break; // , (activate selected menu-item) case NUX_VK_SPACE: case NUX_VK_ENTER: case NUX_KP_ENTER: if (IsMenuItemSelectable(_current_item_index)) { ActivateItem(GetNthItems(_current_item_index)); Hide(); } break; default: break; } } void QuicklistView::EnableQuicklistForTesting(bool enable_testing) { _enable_quicklist_for_testing = enable_testing; } void QuicklistView::SetQuicklistPosition(int tip_x, int tip_y) { _anchorX = tip_x; _anchorY = tip_y; if (!_enable_quicklist_for_testing) { if (!_item_list.empty()) { auto* us = UScreen::GetDefault(); int ql_monitor = us->GetMonitorAtPosition(_anchorX, _anchorY); auto const& ql_monitor_geo = us->GetMonitorGeometry(ql_monitor); auto launcher_position = Settings::Instance().launcher_position(); if (launcher_position == LauncherPosition::LEFT) { int offscreen_size = GetBaseY() + GetBaseHeight() - (ql_monitor_geo.y + ql_monitor_geo.height); if (offscreen_size > 0) _top_size = offscreen_size + TOP_SIZE; else _top_size = TOP_SIZE; } else { int offscreen_size_left = ql_monitor_geo.x - (_anchorX - GetBaseWidth() / 2); int offscreen_size_right = _anchorX + GetBaseWidth()/2 - (ql_monitor_geo.x + ql_monitor_geo.width); int half_size = (GetBaseWidth() / 2) - _padding.CP(cv_) - CORNER_RADIUS.CP(cv_) - (ANCHOR_HEIGHT.CP(cv_) / 2); if (offscreen_size_left > 0) _top_size = half_size - offscreen_size_left; else if (offscreen_size_right > 0) _top_size = half_size + offscreen_size_right; else _top_size = half_size; } SetXY(CalculateX(), CalculateY()); } else { _top_size = 0; SetXY(CalculateX(), CalculateY()); } } } void QuicklistView::ShowQuicklistWithTipAt(int x, int y, bool restore_input_focus) { SetQuicklistPosition(x, y); Show(restore_input_focus); } void QuicklistView::Show(bool restore_input_focus) { if (!IsVisible()) { _restore_input_focus = restore_input_focus; CairoBaseWindow::Show(); GrabPointer(); GrabKeyboard(); } } void QuicklistView::Hide() { if (IsVisible() && !_enable_quicklist_for_testing) { CancelItemsPrelightStatus(); CaptureMouseDownAnyWhereElse(false); UnGrabPointer(); UnGrabKeyboard(); CairoBaseWindow::Hide(); if (_current_item_index != -1) { selection_change.emit(); _current_item_index = -1; } } } void QuicklistView::HideAndEndQuicklistNav() { Hide(); // inform Launcher we switch back to Launcher key-nav UBusManager::SendMessage(UBUS_QUICKLIST_END_KEY_NAV); } void QuicklistView::Draw(nux::GraphicsEngine& gfxContext, bool forceDraw) { CairoBaseWindow::Draw(gfxContext, forceDraw); nux::Geometry base(GetGeometry()); base.x = 0; base.y = 0; gfxContext.PushClippingRectangle(base); for (auto const& item : _item_list) { if (item->GetVisible()) item->ProcessDraw(gfxContext, forceDraw); } gfxContext.PopClippingRectangle(); } void QuicklistView::DrawContent(nux::GraphicsEngine& GfxContext, bool force_draw) {} void QuicklistView::PreLayoutManagement() { int MaxItemWidth = 0; int TotalItemHeight = 0; for (auto const& item : _item_list) { // Make sure item is in layout if it should be if (!item->GetVisible()) { _item_layout->RemoveChildObject(item.GetPointer()); continue; } else if (!item->GetParentObject()) { _item_layout->AddView(item.GetPointer(), 1, nux::MINOR_POSITION_CENTER, nux::MINOR_SIZE_FULL); } nux::Size const& text_extents = item->GetTextExtents(); MaxItemWidth = std::max(MaxItemWidth, text_extents.width); TotalItemHeight += text_extents.height; } int rotated_anchor_height = 0; if (Settings::Instance().launcher_position() == LauncherPosition::BOTTOM) rotated_anchor_height = ANCHOR_WIDTH; if (TotalItemHeight < ANCHOR_HEIGHT.CP(cv_)) { int b = (ANCHOR_HEIGHT.CP(cv_) - TotalItemHeight) / 2 + _padding.CP(cv_) + CORNER_RADIUS.CP(cv_) + rotated_anchor_height; int t = b + OFFSET_CORRECTION.CP(cv_) - rotated_anchor_height; _top_space->SetMinimumHeight(t); _top_space->SetMaximumHeight(t); _bottom_space->SetMinimumHeight(b); _bottom_space->SetMaximumHeight(b); } else { int b = _padding.CP(cv_) + CORNER_RADIUS.CP(cv_) + rotated_anchor_height; int t = b + OFFSET_CORRECTION.CP(cv_) - rotated_anchor_height; _top_space->SetMinimumHeight(t); _top_space->SetMaximumHeight(t); _bottom_space->SetMinimumHeight(b); _bottom_space->SetMaximumHeight(b); } _item_layout->SetMinimumWidth(MaxItemWidth); CairoBaseWindow::PreLayoutManagement(); } long QuicklistView::PostLayoutManagement(long LayoutResult) { long result = CairoBaseWindow::PostLayoutManagement(LayoutResult); UpdateTexture(); int width = 0; if (Settings::Instance().launcher_position() == LauncherPosition::LEFT) width = ANCHOR_WIDTH; int x = RawPixel(_padding + width + CORNER_RADIUS + OFFSET_CORRECTION).CP(cv_); int y = _top_space->GetMinimumHeight(); for (auto const& item : _item_list) { if (!item->GetVisible()) continue; item->SetBaseX(x); item->SetBaseY(y); y += item->GetBaseHeight(); } // We must correct the width of line separators. The rendering of the separator can be smaller than the width of the // quicklist. The reason for that is, the quicklist width is determined by the largest entry it contains. That size is // only after MaxItemWidth is computed in QuicklistView::PreLayoutManagement. // The setting of the separator width is done here after the Layout cycle for this widget is over. The width of the separator // has bee set correctly during the layout cycle, but the cairo rendering still need to be adjusted. unsigned separator_width = _item_layout->GetBaseWidth(); for (auto const& item : _item_list) { if (item->GetVisible() && item->GetCairoSurfaceWidth() != separator_width) { // Compute textures of the item. item->UpdateTexture(); } } return result; } void QuicklistView::RecvCairoTextChanged(QuicklistMenuItem* cairo_text) { _cairo_text_has_changed = true; } void QuicklistView::RecvCairoTextColorChanged(QuicklistMenuItem* cairo_text) { NeedRedraw(); } void QuicklistView::RecvItemMouseClick(QuicklistMenuItem* item, int x, int y) { _mouse_down = false; if (IsVisible() && item->GetEnabled()) { // Check if the mouse was released over an item and emit the signal CheckAndEmitItemSignal(x + item->GetBaseX(), y + item->GetBaseY()); Hide(); } } void QuicklistView::CheckAndEmitItemSignal(int x, int y) { nux::Geometry geo; for (auto const& item : _item_list) { if (!item->GetVisible()) continue; geo = item->GetGeometry(); geo.width = _item_layout->GetBaseWidth(); if (geo.IsPointInside(x, y)) { // An action is performed: send the signal back to the application ActivateItem(item.GetPointer()); } } } void QuicklistView::ActivateItem(QuicklistMenuItem* item) { if (!item) return; item->Activate(); } void QuicklistView::RecvItemMouseRelease(QuicklistMenuItem* item, int x, int y) { _mouse_down = false; if (IsVisible() && item->GetEnabled()) { // Check if the mouse was released over an item and emit the signal CheckAndEmitItemSignal(x + item->GetBaseX(), y + item->GetBaseY()); Hide(); } } void QuicklistView::CancelItemsPrelightStatus() { for (auto const& item : _item_list) item->Select(false); } void QuicklistView::RecvItemMouseDrag(QuicklistMenuItem* item, int x, int y) { nux::Geometry geo; for (auto const& it : _item_list) { int item_index = GetItemIndex(it.GetPointer()); if (!IsMenuItemSelectable(item_index)) continue; geo = it->GetGeometry(); geo.width = _item_layout->GetBaseWidth(); if (geo.IsPointInside(x + item->GetBaseX(), y + item->GetBaseY())) { SelectItem(item_index); } } } void QuicklistView::RecvItemMouseEnter(QuicklistMenuItem* item) { int item_index = GetItemIndex(item); SelectItem(item_index); } void QuicklistView::RecvItemMouseLeave(QuicklistMenuItem* item) { int item_index = GetItemIndex(item); if (item_index < 0 || item_index == _current_item_index) SelectItem(-1); } void QuicklistView::RecvMouseDown(int x, int y, unsigned long button_flags, unsigned long key_flags) { // if (IsVisible ()) // { // CaptureMouseDownAnyWhereElse (false); // UnGrabPointer (); // EnableInputWindow (false); // ShowWindow (false); // } } void QuicklistView::RecvMouseUp(int x, int y, unsigned long button_flags, unsigned long key_flags) { // Check if the mouse was released over an item and emit the signal CheckAndEmitItemSignal(x, y); } void QuicklistView::RecvMouseClick(int x, int y, unsigned long button_flags, unsigned long key_flags) { if (IsVisible()) { Hide(); } } void QuicklistView::RecvMouseMove(int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags) { } void QuicklistView::RecvMouseDrag(int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags) { } void QuicklistView::RecvMouseDownOutsideOfQuicklist(int x, int y, unsigned long button_flags, unsigned long key_flags) { Hide(); } nux::Area* QuicklistView::FindAreaUnderMouse(const nux::Point& mouse_position, nux::NuxEventType event_type) { auto launcher_position = Settings::Instance().launcher_position(); if ((launcher_position == LauncherPosition::LEFT && (mouse_position.x > _anchorX)) || (launcher_position == LauncherPosition::BOTTOM && (mouse_position.y < _anchorY))) { return (CairoBaseWindow::FindAreaUnderMouse(mouse_position, event_type)); } return nullptr; } void QuicklistView::RemoveAllMenuItem() { _item_layout->Clear(); _item_list.clear(); _cairo_text_has_changed = true; QueueRelayout(); } void QuicklistView::AddMenuItem(QuicklistMenuItem* item) { if (!item) return; item->sigTextChanged.connect(sigc::mem_fun(this, &QuicklistView::RecvCairoTextChanged)); item->sigColorChanged.connect(sigc::mem_fun(this, &QuicklistView::RecvCairoTextColorChanged)); item->sigMouseClick.connect(sigc::mem_fun(this, &QuicklistView::RecvItemMouseClick)); item->sigMouseReleased.connect(sigc::mem_fun(this, &QuicklistView::RecvItemMouseRelease)); item->sigMouseEnter.connect(sigc::mem_fun(this, &QuicklistView::RecvItemMouseEnter)); item->sigMouseLeave.connect(sigc::mem_fun(this, &QuicklistView::RecvItemMouseLeave)); item->sigMouseDrag.connect(sigc::mem_fun(this, &QuicklistView::RecvItemMouseDrag)); item->SetScale(cv_->DPIScale()); _item_list.push_back(QuicklistMenuItem::Ptr(item)); _cairo_text_has_changed = true; QueueRelayout(); } void QuicklistView::RenderQuicklistView() { } int QuicklistView::GetNumItems() { return _item_list.size(); } QuicklistMenuItem* QuicklistView::GetNthItems(int index) { if (index < (int)_item_list.size()) { int i = 0; for (auto const& item : _item_list) { if (i++ == index) return item.GetPointer(); } } return nullptr; } int QuicklistView::GetItemIndex(QuicklistMenuItem* item) { int index = -1; for (auto const& it : _item_list) { ++index; if (it == item) return index; } return index; } QuicklistMenuItemType QuicklistView::GetNthType(int index) { QuicklistMenuItem* item = GetNthItems(index); if (item) return item->GetItemType(); return QuicklistMenuItemType::UNKNOWN; } std::list QuicklistView::GetChildren() { return _item_list; } void QuicklistView::SelectFirstItem() { SelectItem(0); } void ql_tint_dot_hl(cairo_t* cr, gfloat scale, gint width, gint height, gfloat hl_x, gfloat hl_y, gfloat hl_size, nux::Color const& tint_color, nux::Color const& hl_color, nux::Color const& dot_color) { cairo_pattern_t* dots_pattern = NULL; cairo_pattern_t* hl_pattern = NULL; // create context for dot-pattern nux::CairoGraphics dots_surf(CAIRO_FORMAT_ARGB32, 4 * scale, 4 * scale); cairo_surface_set_device_scale(dots_surf.GetSurface(), scale, scale); cairo_t* dots_cr = dots_surf.GetInternalContext(); // clear normal context cairo_scale(cr, 1.0f, 1.0f); cairo_set_source_rgba(cr, 0.0f, 0.0f, 0.0f, 0.0f); cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_paint(cr); // prepare drawing for normal context cairo_set_operator(cr, CAIRO_OPERATOR_OVER); // create path in normal context cairo_rectangle(cr, 0.0f, 0.0f, (gdouble) width, (gdouble) height); // fill path of normal context with tint cairo_set_source_rgba(cr, tint_color.red, tint_color.green, tint_color.blue, tint_color.alpha); cairo_fill_preserve(cr); // create pattern in dot-context cairo_set_operator(dots_cr, CAIRO_OPERATOR_CLEAR); cairo_paint(dots_cr); cairo_scale(dots_cr, 1.0f, 1.0f); cairo_set_operator(dots_cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgba(dots_cr, dot_color.red, dot_color.green, dot_color.blue, dot_color.alpha); cairo_rectangle(dots_cr, 0.0f, 0.0f, 1.0f, 1.0f); cairo_fill(dots_cr); cairo_rectangle(dots_cr, 2.0f, 2.0f, 1.0f, 1.0f); cairo_fill(dots_cr); dots_pattern = cairo_pattern_create_for_surface(dots_surf.GetSurface()); } void ql_setup(cairo_surface_t** surf, cairo_t** cr, gboolean outline, gboolean negative) { // clear context cairo_scale(*cr, 1.0f, 1.0f); if (outline) { cairo_set_source_rgba(*cr, 0.0f, 0.0f, 0.0f, 0.0f); cairo_set_operator(*cr, CAIRO_OPERATOR_CLEAR); } else { cairo_set_operator(*cr, CAIRO_OPERATOR_OVER); if (negative) cairo_set_source_rgba(*cr, 0.0f, 0.0f, 0.0f, 0.0f); else cairo_set_source_rgba(*cr, 1.0f, 1.0f, 1.0f, 1.0f); } cairo_paint(*cr); } void ql_compute_full_mask_path(cairo_t* cr, gfloat anchor_width, gfloat anchor_height, gfloat width, gfloat height, gint upper_size, gfloat radius, guint pad) { // On the right of the icon: On the top of the icon: // 0 1 2 3 0 1 2 3 // +--+--------+--+ +--+-----------+--+ // | | | | // + 14 + 4 14 + + 4 // | | | | // | | | | // | | | | // + 13 | | | // / | | | // / | | | // + 12 | | | // \ | | | // \ | | | // 11 + | | | // | | 13 + + 5 // | | | 10 8 | // | | 12 +--+--+ +--+--+ 6 // 10 + + 5 11 \ / 7 // | | \ / // +--+--------+--+ 6 + // 9 8 7 9 gfloat padding = pad; int ZEROPOINT5 = 0.0f; auto launcher_position = Settings::Instance().launcher_position(); //gint dynamic_size = height - 2*radius - 2*padding - anchor_height; //gint upper_dynamic_size = upper_size; //gint lower_dynamic_size = dynamic_size - upper_dynamic_size; int size = 0; if (launcher_position == LauncherPosition::LEFT) size = height; else size = width; gfloat HeightToAnchor = ((gfloat) size - 2.0f * radius - anchor_height - 2 * padding) / 2.0f; if (HeightToAnchor < 0.0f) { g_warning("Anchor-height and corner-radius a higher than whole texture!"); return; } if (upper_size >= 0) { if (upper_size > size - 2.0f * radius - anchor_height - 2 * padding) { //g_warning ("[_compute_full_mask_path] incorrect upper_size value"); HeightToAnchor = 0; } else { HeightToAnchor = size - 2.0f * radius - anchor_height - 2 * padding - upper_size; } } else { if (launcher_position == LauncherPosition::LEFT) HeightToAnchor = (size - 2.0f * radius - anchor_height - 2 * padding) / 2.0f; else HeightToAnchor = size - 2.0f * radius - anchor_height - 2 * padding; } cairo_translate(cr, -0.5f, -0.5f); // create path if (launcher_position == LauncherPosition::LEFT) { cairo_move_to(cr, padding + anchor_width + radius + ZEROPOINT5, padding + ZEROPOINT5); // Point 1 cairo_line_to(cr, width - padding - radius, padding + ZEROPOINT5); // Point 2 cairo_arc(cr, width - padding - radius + ZEROPOINT5, padding + radius + ZEROPOINT5, radius, -90.0f * G_PI / 180.0f, 0.0f * G_PI / 180.0f); // Point 4 cairo_line_to(cr, (gdouble) width - padding + ZEROPOINT5, (gdouble) height - radius - padding + ZEROPOINT5); // Point 5 cairo_arc(cr, (gdouble) width - padding - radius + ZEROPOINT5, (gdouble) height - padding - radius + ZEROPOINT5, radius, 0.0f * G_PI / 180.0f, 90.0f * G_PI / 180.0f); // Point 7 cairo_line_to(cr, anchor_width + padding + radius + ZEROPOINT5, (gdouble) height - padding + ZEROPOINT5); // Point 8 cairo_arc(cr, anchor_width + padding + radius + ZEROPOINT5, (gdouble) height - padding - radius, radius, 90.0f * G_PI / 180.0f, 180.0f * G_PI / 180.0f); // Point 10 cairo_line_to(cr, padding + anchor_width + ZEROPOINT5, (gdouble) height - padding - radius - HeightToAnchor + ZEROPOINT5); // Point 11 cairo_line_to(cr, padding + ZEROPOINT5, (gdouble) height - padding - radius - HeightToAnchor - anchor_height / 2.0f + ZEROPOINT5); // Point 12 cairo_line_to(cr, padding + anchor_width + ZEROPOINT5, (gdouble) height - padding - radius - HeightToAnchor - anchor_height + ZEROPOINT5); // Point 13 cairo_line_to(cr, padding + anchor_width + ZEROPOINT5, padding + radius + ZEROPOINT5); // Point 14 cairo_arc(cr, padding + anchor_width + radius + ZEROPOINT5, padding + radius + ZEROPOINT5, radius, 180.0f * G_PI / 180.0f, 270.0f * G_PI / 180.0f); } else { cairo_move_to(cr, padding + radius + ZEROPOINT5, padding + ZEROPOINT5); // Point 1 cairo_line_to(cr, width - padding - radius, padding + ZEROPOINT5); // Point 2 cairo_arc(cr, width - padding - radius + ZEROPOINT5, padding + radius + ZEROPOINT5, radius, -90.0f * G_PI / 180.0f, 0.0f * G_PI / 180.0f); // Point 4 cairo_line_to(cr, (gdouble) width - padding + ZEROPOINT5, (gdouble) height - radius - anchor_width - padding + ZEROPOINT5); // Point 5 cairo_arc(cr, (gdouble) width - padding - radius + ZEROPOINT5, (gdouble) height - padding - anchor_width - radius + ZEROPOINT5, radius, 0.0f * G_PI / 180.0f, 90.0f * G_PI / 180.0f); // Point 7 cairo_line_to(cr, (gdouble) width - padding - radius - HeightToAnchor + ZEROPOINT5, height - padding - anchor_width + ZEROPOINT5); // Point 8 cairo_line_to(cr, (gdouble) width - padding - radius - HeightToAnchor - anchor_height / 2.0f + ZEROPOINT5, height - padding + ZEROPOINT5); // Point 9 cairo_line_to(cr, (gdouble) width - padding - radius - HeightToAnchor - anchor_height + ZEROPOINT5, height - padding - anchor_width + ZEROPOINT5); // Point 10 cairo_arc(cr, padding + radius + ZEROPOINT5, (gdouble) height - padding - anchor_width - radius, radius, 90.0f * G_PI / 180.0f, 180.0f * G_PI / 180.0f); // Point 11 cairo_line_to(cr, padding + ZEROPOINT5, (gdouble) height - padding -anchor_width - radius + ZEROPOINT5); // Point 13 cairo_line_to(cr, padding + ZEROPOINT5, padding + radius + ZEROPOINT5); // Point 14 cairo_arc(cr, padding + radius + ZEROPOINT5, padding + radius + ZEROPOINT5, radius, 180.0f * G_PI / 180.0f, 270.0f * G_PI / 180.0f); } cairo_close_path(cr); } void ql_compute_mask(cairo_t* cr) { cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_fill_preserve(cr); } void ql_compute_outline(cairo_t* cr, gfloat line_width, nux::Color const& line_color, gfloat size) { cairo_pattern_t* pattern = NULL; float x = 0.0f; float y = 0.0f; float offset = 2.5f * ANCHOR_WIDTH / size; cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); pattern = cairo_pattern_create_linear(x, y, size, y); cairo_pattern_add_color_stop_rgba(pattern, 0.0f, line_color.red, line_color.green, line_color.blue, line_color.alpha); cairo_pattern_add_color_stop_rgba(pattern, offset, line_color.red, line_color.green, line_color.blue, line_color.alpha); cairo_pattern_add_color_stop_rgba(pattern, 1.1f * offset, line_color.red * 0.65f, line_color.green * 0.65f, line_color.blue * 0.65f, line_color.alpha); cairo_pattern_add_color_stop_rgba(pattern, 1.0f, line_color.red * 0.65f, line_color.green * 0.65f, line_color.blue * 0.65f, line_color.alpha); cairo_set_source(cr, pattern); cairo_set_line_width(cr, line_width); cairo_stroke(cr); cairo_pattern_destroy(pattern); } void ql_draw(cairo_t* cr, gboolean outline, gfloat line_width, nux::Color const& color, gboolean negative, gboolean stroke) { // prepare drawing cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); // actually draw the mask if (outline) { cairo_set_line_width(cr, line_width); cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha); } else { if (negative) cairo_set_source_rgba(cr, 1.0f, 1.0f, 1.0f, 1.0f); else cairo_set_source_rgba(cr, 0.0f, 0.0f, 0.0f, 0.0f); } // stroke or fill? if (stroke) cairo_stroke_preserve(cr); else cairo_fill_preserve(cr); } void ql_finalize(cairo_t** cr, gboolean outline, gfloat line_width, nux::Color const& color, gboolean negative, gboolean stroke) { // prepare drawing cairo_set_operator(*cr, CAIRO_OPERATOR_SOURCE); // actually draw the mask if (outline) { cairo_set_line_width(*cr, line_width); cairo_set_source_rgba(*cr, color.red, color.green, color.blue, color.alpha); } else { if (negative) cairo_set_source_rgba(*cr, 1.0f, 1.0f, 1.0f, 1.0f); else cairo_set_source_rgba(*cr, 0.0f, 0.0f, 0.0f, 0.0f); } // stroke or fill? if (stroke) cairo_stroke(*cr); else cairo_fill(*cr); } void ql_compute_full_outline_shadow( cairo_t* cr, cairo_surface_t* surf, gfloat width, gfloat height, gfloat anchor_width, gfloat anchor_height, gint upper_size, gfloat corner_radius, guint blur_coeff, nux::Color const& rgba_shadow, gfloat line_width, gint padding_size, nux::Color const& rgba_line) { ql_setup(&surf, &cr, TRUE, FALSE); ql_compute_full_mask_path(cr, anchor_width, anchor_height, width, height, upper_size, corner_radius, padding_size); ql_draw(cr, TRUE, line_width, rgba_shadow, FALSE, FALSE); nux::CairoGraphics dummy(CAIRO_FORMAT_A1, 1, 1); dummy.BlurSurface(blur_coeff, surf); ql_compute_mask(cr); ql_compute_outline(cr, line_width, rgba_line, width); } void ql_compute_full_mask( cairo_t* cr, cairo_surface_t* surf, gfloat width, gfloat height, gfloat radius, gfloat anchor_width, gfloat anchor_height, gint upper_size, gboolean negative, gboolean outline, gfloat line_width, gint padding_size, nux::Color const& rgba) { ql_setup(&surf, &cr, outline, negative); ql_compute_full_mask_path(cr, anchor_width, anchor_height, width, height, upper_size, radius, padding_size); ql_finalize(&cr, outline, line_width, rgba, negative, outline); } void QuicklistView::UpdateTexture() { if (!_cairo_text_has_changed) return; SetQuicklistPosition(_anchorX, _anchorY); RawPixel size_above_anchor = _item_list.empty() ? RawPixel(-1) : _top_size; int width = GetBaseWidth(); int height = GetBaseHeight(); auto const& deco_style = decoration::Style::Get(); float dpi_scale = cv_->DPIScale(); float blur_coef = std::round(deco_style->ActiveShadowRadius() * dpi_scale / 2.0f); nux::CairoGraphics cairo_bg(CAIRO_FORMAT_ARGB32, width, height); nux::CairoGraphics cairo_mask(CAIRO_FORMAT_ARGB32, width, height); nux::CairoGraphics cairo_outline(CAIRO_FORMAT_ARGB32, width, height); cairo_surface_set_device_scale(cairo_bg.GetSurface(), dpi_scale, dpi_scale); cairo_surface_set_device_scale(cairo_mask.GetSurface(), dpi_scale, dpi_scale); cairo_surface_set_device_scale(cairo_outline.GetSurface(), dpi_scale, dpi_scale); cairo_t* cr_bg = cairo_bg.GetInternalContext(); cairo_t* cr_mask = cairo_mask.GetInternalContext(); cairo_t* cr_outline = cairo_outline.GetInternalContext(); nux::Color tint_color(0.0f, 0.0f, 0.0f, HasBlurredBackground() ? 0.60f : 1.0f); nux::Color hl_color(1.0f, 1.0f, 1.0f, 0.35f); nux::Color dot_color(1.0f, 1.0f, 1.0f, 0.03f); nux::Color shadow_color(deco_style->ActiveShadowColor()); nux::Color outline_color(1.0f, 1.0f, 1.0f, 0.40f); nux::Color mask_color(1.0f, 1.0f, 1.0f, 1.00f); ql_tint_dot_hl(cr_bg, dpi_scale, width / dpi_scale, height / dpi_scale, width / 2.0f, 0, nux::Max(width / 1.6f, height / 1.6f), tint_color, hl_color, dot_color); ql_compute_full_outline_shadow ( cr_outline, cairo_outline.GetSurface(), width / dpi_scale, height / dpi_scale, ANCHOR_WIDTH, ANCHOR_HEIGHT, size_above_anchor, CORNER_RADIUS, blur_coef, shadow_color, 1.0f * dpi_scale, _padding, outline_color); ql_compute_full_mask( cr_mask, cairo_mask.GetSurface(), width / dpi_scale, height / dpi_scale, CORNER_RADIUS, // radius, ANCHOR_WIDTH, // anchor_width, ANCHOR_HEIGHT, // anchor_height, size_above_anchor, // upper_size, true, // negative, false, // outline, 1.0, // line_width, _padding, // padding_size, mask_color); texture_bg_ = texture_ptr_from_cairo_graphics(cairo_bg); texture_mask_ = texture_ptr_from_cairo_graphics(cairo_mask); texture_outline_ = texture_ptr_from_cairo_graphics(cairo_outline); _cairo_text_has_changed = false; // Request a redraw, so this area will be added to Compiz list of dirty areas. NeedRedraw(); } void QuicklistView::PositionChildLayout(float offsetX, float offsetY) { } void QuicklistView::LayoutWindowElements() { } void QuicklistView::NotifyConfigurationChange(int width, int height) { } void QuicklistView::SetText(std::string const& text) { if (_labelText == text) return; _labelText = text; UpdateTexture(); } // Introspection std::string QuicklistView::GetName() const { return "Quicklist"; } void QuicklistView::AddProperties(debug::IntrospectionData& introspection) { introspection .add(GetAbsoluteGeometry()) .add("base_x", GetBaseX()) .add("base_y", GetBaseY()) .add("base", nux::Point(GetBaseX(), GetBaseY())) .add("active", IsVisible()); } // // Key navigation // bool QuicklistView::InspectKeyEvent(unsigned int eventType, unsigned int keysym, const char* character) { // The Quicklist accepts all key inputs. return true; } QuicklistMenuItem* QuicklistView::GetSelectedMenuItem() { return GetNthItems(_current_item_index); } debug::Introspectable::IntrospectableList QuicklistView::GetIntrospectableChildren() { debug::Introspectable::IntrospectableList list(_item_list.size()); for (auto const& item: _item_list) list.push_back(item.GetPointer()); return list; } } // NAMESPACE