2
* Copyright (C) 2010 Canonical Ltd
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License version 3 as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Authored by: Gord Allott <gord.allott@canonical.com>
23
#include <gio/gdesktopappinfo.h>
24
#include <glib/gi18n-lib.h>
26
#include <Nux/Button.h>
30
#include <NuxCore/Logger.h>
31
#include <UnityCore/GLibWrapper.h>
32
#include <UnityCore/RadioOptionFilter.h>
33
#include <UnityCore/Variant.h>
34
#include <Nux/HLayout.h>
35
#include <Nux/LayeredLayout.h>
37
#include <NuxCore/Logger.h>
38
#include "HudButton.h"
39
#include "UBusMessages.h"
40
#include "DashStyle.h"
49
nux::logging::Logger logger("unity.hud.view");
51
const std::string default_text = _("Type your command");
52
const int grow_anim_length = 90 * 1000;
53
const int pause_before_grow_length = 32 * 1000;
56
NUX_IMPLEMENT_OBJECT_TYPE(View);
59
: nux::View(NUX_TRACKER_LOCATION)
63
, last_known_height_(0)
65
, timeline_need_more_draw_(false)
68
renderer_.SetOwner(this);
69
renderer_.need_redraw.connect([this] () {
75
rop.SrcBlend = GL_ONE;
76
rop.DstBlend = GL_ONE_MINUS_SRC_ALPHA;
79
search_bar_->key_down.connect (sigc::mem_fun (this, &View::OnKeyDown));
81
search_bar_->activated.connect ([&]()
83
search_activated.emit(search_bar_->search_string);
86
search_bar_->text_entry()->SetLoseKeyFocusOnKeyNavDirectionUp(false);
87
search_bar_->text_entry()->SetLoseKeyFocusOnKeyNavDirectionDown(false);
89
search_bar_->text_entry()->key_nav_focus_change.connect([&](nux::Area *area, bool receiving, nux::KeyNavDirection direction)
91
// We get here when the Hud closes.
92
// The TextEntry should always have the keyboard focus as long as the hud is open.
95
return;// early return on empty button list
99
if (!buttons_.empty())
101
// If the search_bar gets focus, fake focus the first button if it exists
102
buttons_.back()->fake_focused = true;
107
// The hud is closing and there are HudButtons visible. Remove the fake_focus.
108
// There should be only one HudButton with the fake_focus set to true.
109
std::list<HudButton::Ptr>::iterator it;
110
for(it = buttons_.begin(); it != buttons_.end(); ++it)
112
if ((*it)->fake_focused)
114
(*it)->fake_focused = false;
120
mouse_down.connect(sigc::mem_fun(this, &View::OnMouseButtonDown));
127
RemoveChild(search_bar_.GetPointer());
128
for (auto button = buttons_.begin(); button != buttons_.end(); button++)
130
RemoveChild((*button).GetPointer());
134
void View::ProcessGrowShrink()
136
float diff = g_get_monotonic_time() - start_time_;
137
int target_height = content_layout_->GetGeometry().height;
138
// only animate if we are after our defined pause time
139
if (diff > pause_before_grow_length)
141
float progress = (diff - pause_before_grow_length) / grow_anim_length;
142
int last_height = last_known_height_;
145
if (last_height < target_height)
148
new_height = last_height + ((target_height - last_height) * progress);
153
new_height = last_height - ((last_height - target_height) * progress);
156
LOG_DEBUG(logger) << "resizing to " << target_height << " (" << new_height << ")"
157
<< "View height: " << GetGeometry().height;
158
current_height_ = new_height;
163
if (diff > grow_anim_length + pause_before_grow_length)
165
// ensure we are at our final location and update last known height
166
current_height_ = target_height;
167
last_known_height_ = target_height;
168
timeline_need_more_draw_ = false;
173
void View::ResetToDefault()
175
search_bar_->search_string = "";
176
search_bar_->search_hint = default_text;
179
void View::Relayout()
181
nux::Geometry geo = GetGeometry();
182
content_geo_ = GetBestFitGeometry(geo);
183
LOG_DEBUG(logger) << "content_geo: " << content_geo_.width << "x" << content_geo_.height;
185
layout_->SetMinimumWidth(content_geo_.width);
186
layout_->SetMaximumWidth(content_geo_.width);
187
layout_->SetMaximumHeight(content_geo_.height);
188
//layout_->SetMinMaxSize(content_geo_.width, content_geo_.height);
193
long View::PostLayoutManagement(long LayoutResult)
196
if (GetGeometry().height != last_known_height_)
198
// Start the timeline of drawing the dash resize
199
if (timeline_need_more_draw_)
201
// already started, just reset the last known height
202
last_known_height_ = current_height_;
205
timeline_need_more_draw_ = true;
206
start_time_ = g_get_monotonic_time();
214
nux::View* View::default_focus() const
216
return search_bar_->text_entry();
219
void View::SetQueries(Hud::Queries queries)
221
// remove the previous children
222
for (auto button : buttons_)
224
RemoveChild(button.GetPointer());
227
selected_button_ = 0;
230
button_views_->Clear();
232
for (auto query = queries.begin(); query != queries.end(); query++)
237
HudButton::Ptr button(new HudButton());
238
buttons_.push_front(button);
239
button->SetQuery(*query);
241
button_views_->AddView(button.GetPointer(), 0, nux::MINOR_POSITION_LEFT);
243
button->click.connect([&](nux::View* view) {
244
query_activated.emit(dynamic_cast<HudButton*>(view)->GetQuery());
247
button->key_nav_focus_activate.connect([&](nux::Area *area) {
248
query_activated.emit(dynamic_cast<HudButton*>(area)->GetQuery());
251
button->key_nav_focus_change.connect([&](nux::Area *area, bool recieving, KeyNavDirection direction){
253
query_selected.emit(dynamic_cast<HudButton*>(area)->GetQuery());
256
// You should never decrement end(). We should fix this loop.
257
button->is_rounded = (query == --(queries.end())) ? true : false;
258
button->fake_focused = (query == (queries.begin())) ? true : false;
260
button->SetMinimumWidth(941);
264
selected_button_ = 1;
270
void View::SetIcon(std::string icon_name)
272
LOG_DEBUG(logger) << "Setting icon to " << icon_name;
273
icon_->SetByIconName(icon_name.c_str(), icon_size);
277
// Gives us the width and height of the contents that will give us the best "fit",
278
// which means that the icons/views will not have uneccessary padding, everything will
280
nux::Geometry View::GetBestFitGeometry(nux::Geometry const& for_geo)
282
//FIXME - remove magic values, replace with scalable text depending on DPI
283
// requires smarter font settings really...
284
int width, height = 0;
288
LOG_DEBUG (logger) << "best fit is, " << width << ", " << height;
290
return nux::Geometry(0, 0, width, height);
293
void View::AboutToShow()
295
renderer_.AboutToShow();
298
void View::AboutToHide()
300
renderer_.AboutToHide();
303
void View::SetWindowGeometry(nux::Geometry const& absolute_geo, nux::Geometry const& geo)
305
window_geometry_ = geo;
306
window_geometry_.x = 0;
307
window_geometry_.y = 0;
308
absolute_window_geometry_ = absolute_geo;
313
const int top_spacing = 9;
314
const int content_width = 941;
315
const int icon_vertical_margin = 5;
316
const int spacing_between_icon_and_content = 8;
317
const int bottom_padding = 10;
320
void View::SetupViews()
322
nux::VLayout* super_layout = new nux::VLayout();
323
layout_ = new nux::HLayout();
325
// fill icon layout with icon
326
icon_ = new Icon("", icon_size, true);
327
nux::Layout* icon_layout = new nux::VLayout();
329
icon_layout->SetVerticalExternalMargin(icon_vertical_margin);
330
icon_layout->AddView(icon_.GetPointer(), 0, nux::MINOR_POSITION_LEFT, nux::MINOR_SIZE_FULL);
331
layout_->AddLayout(icon_layout, 0, nux::MINOR_POSITION_TOP, nux::MINOR_SIZE_MATCHCONTENT);
334
// add padding to layout between icon and content
335
layout_->AddLayout(new nux::SpaceLayout(spacing_between_icon_and_content,
336
spacing_between_icon_and_content,
337
spacing_between_icon_and_content,
338
spacing_between_icon_and_content), 0);
340
// fill the content layout
341
content_layout_ = new nux::VLayout();
343
// add the top spacing
344
content_layout_->AddLayout(new nux::SpaceLayout(top_spacing,top_spacing,top_spacing,top_spacing), 0);
346
// add the search bar to the composite
347
search_bar_ = new unity::SearchBar(content_width, true);
348
search_bar_->disable_glow = true;
349
search_bar_->search_hint = default_text;
350
search_bar_->search_changed.connect(sigc::mem_fun(this, &View::OnSearchChanged));
351
AddChild(search_bar_.GetPointer());
352
content_layout_->AddView(search_bar_.GetPointer(), 0, nux::MINOR_POSITION_LEFT);
354
button_views_ = new nux::VLayout();
355
button_views_->SetMaximumWidth(content_width);
357
content_layout_->AddLayout(button_views_.GetPointer(), 1, nux::MINOR_POSITION_LEFT);
358
content_layout_->AddLayout(new nux::SpaceLayout(bottom_padding,
364
layout_->AddLayout(content_layout_.GetPointer(), 1, nux::MINOR_POSITION_TOP);
367
super_layout->AddLayout(layout_.GetPointer(), 0);
368
SetLayout(super_layout);
371
void View::OnSearchChanged(std::string const& search_string)
373
LOG_DEBUG(logger) << "got search change";
374
search_changed.emit(search_string);
375
if (search_string.empty())
377
search_bar_->search_hint = default_text;
381
search_bar_->search_hint = "";
386
void View::OnKeyDown (unsigned long event_type, unsigned long keysym,
387
unsigned long event_state, const TCHAR* character,
388
unsigned short key_repeat_count)
390
if (keysym == NUX_VK_ESCAPE)
392
LOG_DEBUG(logger) << "got escape key";
393
ubus.SendMessage(UBUS_HUD_CLOSE_REQUEST);
397
void View::OnMouseButtonDown(int x, int y, unsigned long button, unsigned long key)
399
if (!content_geo_.IsPointInside(x, y))
401
ubus.SendMessage(UBUS_HUD_CLOSE_REQUEST);
405
void View::Draw(nux::GraphicsEngine& gfx_context, bool force_draw)
407
if (timeline_need_more_draw_)
412
nux::Geometry draw_content_geo(layout_->GetGeometry());
413
draw_content_geo.height = current_height_;
414
renderer_.DrawFull(gfx_context, draw_content_geo, absolute_window_geometry_, window_geometry_, true);
417
void View::DrawContent(nux::GraphicsEngine& gfx_context, bool force_draw)
419
nux::Geometry draw_content_geo(layout_->GetGeometry());
420
draw_content_geo.height = current_height_;
422
renderer_.DrawInner(gfx_context, draw_content_geo, absolute_window_geometry_, window_geometry_);
424
gfx_context.PushClippingRectangle(draw_content_geo);
427
nux::GetPainter().PushBackgroundStack();
428
GetLayout()->ProcessDraw(gfx_context, force_draw);
429
nux::GetPainter().PopBackgroundStack();
433
GetLayout()->ProcessDraw(gfx_context, force_draw);
435
gfx_context.PopClippingRectangle();
437
renderer_.DrawInnerCleanup(gfx_context, draw_content_geo, absolute_window_geometry_, window_geometry_);
439
if (timeline_need_more_draw_ && !timeline_id_)
441
timeline_id_ = g_timeout_add(0, [] (gpointer data) -> gboolean
443
View *self = static_cast<View*>(data);
445
self->timeline_id_ = 0;
451
// Keyboard navigation
452
bool View::AcceptKeyNavFocus()
458
std::string View::GetName() const
463
void View::AddProperties(GVariantBuilder* builder)
465
unsigned num_buttons = buttons_.size();
466
variant::BuilderWrapper(builder)
467
.add("selected_button", selected_button_)
468
.add("num_buttons", num_buttons);
471
bool View::InspectKeyEvent(unsigned int eventType,
472
unsigned int key_sym,
473
const char* character)
475
if ((eventType == nux::NUX_KEYDOWN) && (key_sym == NUX_VK_ESCAPE))
477
if (search_bar_->search_string == "")
479
ubus.SendMessage(UBUS_HUD_CLOSE_REQUEST);
483
search_bar_->search_string = "";
484
search_bar_->search_hint = default_text;
491
nux::Area* View::FindKeyFocusArea(unsigned int event_type,
492
unsigned long x11_key_code,
493
unsigned long special_keys_state)
495
nux::KeyNavDirection direction = nux::KEY_NAV_NONE;
496
switch (x11_key_code)
499
direction = nux::KEY_NAV_UP;
502
direction = nux::KEY_NAV_DOWN;
505
direction = nux::KEY_NAV_LEFT;
508
direction = nux::KEY_NAV_RIGHT;
510
case NUX_VK_LEFT_TAB:
511
direction = nux::KEY_NAV_TAB_PREVIOUS;
514
direction = nux::KEY_NAV_TAB_NEXT;
518
// Not sure if Enter should be a navigation key
519
direction = nux::KEY_NAV_ENTER;
522
direction = nux::KEY_NAV_NONE;
527
if ((event_type == nux::NUX_KEYDOWN) && (x11_key_code == NUX_VK_ESCAPE))
529
// Escape key! This is how it works:
530
// -If there is text, clear it and give the focus to the text entry view.
531
// -If there is no text text, then close the hud.
533
if (search_bar_->search_string == "")
535
search_bar_->search_hint = default_text;
536
ubus.SendMessage(UBUS_HUD_CLOSE_REQUEST);
540
search_bar_->search_string = "";
541
search_bar_->search_hint = default_text;
542
return search_bar_->text_entry();
547
if (search_bar_->text_entry()->HasKeyFocus())
549
if (direction == nux::KEY_NAV_NONE ||
550
direction == nux::KEY_NAV_UP ||
551
direction == nux::KEY_NAV_DOWN ||
552
direction == nux::KEY_NAV_LEFT ||
553
direction == nux::KEY_NAV_RIGHT)
555
// We have received a key character or a keyboard arrow Up or Down (navigation keys).
556
// Because we have called "SetLoseKeyFocusOnKeyNavDirectionUp(false);" and "SetLoseKeyFocusOnKeyNavDirectionDown(false);"
557
// on the text entry, the text entry will not loose the keyboard focus.
558
// All that we need to do here is set the fake_focused value on the HudButton.
560
if (!buttons_.empty())
562
if (event_type == nux::NUX_KEYDOWN && direction == nux::KEY_NAV_UP)
564
std::list<HudButton::Ptr>::iterator it;
565
for(it = buttons_.begin(); it != buttons_.end(); ++it)
567
if ((*it)->fake_focused)
569
std::list<HudButton::Ptr>::iterator next = it;
571
if (next != buttons_.end())
573
// The button with the current fake_focus looses it.
574
(*it)->fake_focused = false;
575
// The next button gets the fake_focus
576
(*next)->fake_focused = true;
577
query_selected.emit((*next)->GetQuery());
585
if (event_type == nux::NUX_KEYDOWN && direction == nux::KEY_NAV_DOWN)
587
std::list<HudButton::Ptr>::reverse_iterator rit;
588
for(rit = buttons_.rbegin(); rit != buttons_.rend(); ++rit)
590
if ((*rit)->fake_focused)
592
std::list<HudButton::Ptr>::reverse_iterator next = rit;
594
if(next != buttons_.rend())
596
// The button with the current fake_focus looses it.
597
(*rit)->fake_focused = false;
598
// The next button bellow gets the fake_focus.
599
(*next)->fake_focused = true;
600
query_selected.emit((*next)->GetQuery());
608
return search_bar_->text_entry();
611
if (direction == nux::KEY_NAV_ENTER)
613
// The "Enter" key has been received and the text entry has the key focus.
614
// If one of the button has the fake_focus, we get it to emit the query_activated signal.
615
if (!buttons_.empty())
617
std::list<HudButton::Ptr>::iterator it;
618
for(it = buttons_.begin(); it != buttons_.end(); ++it)
620
if ((*it)->fake_focused)
622
query_activated.emit((*it)->GetQuery());
627
// We still choose the text_entry as the receiver of the key focus.
628
return search_bar_->text_entry();
631
else if (direction == nux::KEY_NAV_NONE)
633
return search_bar_->text_entry();
635
else if (next_object_to_key_focus_area_)
637
return next_object_to_key_focus_area_->FindKeyFocusArea(event_type, x11_key_code, special_keys_state);