Merge lp:~nick-dedekind/unity/lp841899.filter-multi-range into lp:unity

Proposed by Nick Dedekind
Status: Merged
Approved by: Andrea Azzarone
Approved revision: no longer in the source branch.
Merged at revision: 3156
Proposed branch: lp:~nick-dedekind/unity/lp841899.filter-multi-range
Merge into: lp:unity
Diff against target: 1508 lines (+825/-185)
20 files modified
UnityCore/MultiRangeFilter.cpp (+42/-52)
dash/FilterAllButton.cpp (+2/-0)
dash/FilterAllButton.h (+1/-0)
dash/FilterBasicButton.cpp (+2/-0)
dash/FilterBasicButton.h (+1/-0)
dash/FilterFactory.cpp (+1/-1)
dash/FilterGenreButton.cpp (+2/-0)
dash/FilterGenreButton.h (+1/-0)
dash/FilterMultiRangeButton.cpp (+22/-17)
dash/FilterMultiRangeButton.h (+2/-1)
dash/FilterMultiRangeWidget.cpp (+243/-26)
dash/FilterMultiRangeWidget.h (+24/-5)
dash/FilterRatingsButton.cpp (+3/-0)
dash/FilterRatingsButton.h (+1/-0)
tests/CMakeLists.txt (+1/-0)
tests/test_filter_multirange.h (+75/-0)
tests/test_filter_widgets.cpp (+187/-0)
tests/test_lens.cpp (+12/-17)
unity-shared/DashStyle.cpp (+202/-66)
unity-shared/DashStyle.h (+1/-0)
To merge this branch: bzr merge lp:~nick-dedekind/unity/lp841899.filter-multi-range
Reviewer Review Type Date Requested Status
Andrea Azzarone (community) Approve
John Lea (community) Approve
PS Jenkins bot continuous-integration Pending
Review via email: mp+140395@code.launchpad.net

Commit message

Changed click/Dnd behaviour to match requested design. (LP: #841899)

Description of the change

= Problem description =

https://bugs.launchpad.net/ayatana-design/+bug/841899

= The fix =

Changed click/Dnd behaviour to match requested design.
Fixed visual design of active range border.

= Test coverage =

Added unity tests for widget construction, clicking and drag logic.
Updated lens multi-range filter logic tests.

To post a comment you must log in.
Revision history for this message
John Lea (johnlea) :
review: Approve
Revision history for this message
Andrea Azzarone (azzar1) wrote :

+ for (FilterOption::Ptr option : options_)

auto const& option?

260 + auto func_invalidate = [&, geo](std::pair<const MapKey, NuxCairoPtr>& pair)

I think you can remove the & in the closure.

358 +FilterMultiRangeWidget::~FilterMultiRangeWidget()
359 +{
360 +}

Do we really need an empty dtor?

462 + if (mouse_inside == false)
463 + return NULL;

Just use ! + nullptr

481 +void FilterMultiRangeWidget::RecvMouseLeave(int x, int y, unsigned long button_flags, unsigned long key_flags)
482 +{
483 +}

Empty function?

I'll continue the review later.

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> + for (FilterOption::Ptr option : options_)
>
> auto const& option?
>
> 260 + auto func_invalidate = [&, geo](std::pair<const MapKey,
> NuxCairoPtr>& pair)
>
> I think you can remove the & in the closure.
>
> 358 +FilterMultiRangeWidget::~FilterMultiRangeWidget()
> 359 +{
> 360 +}
>
> Do we really need an empty dtor?
>
> 462 + if (mouse_inside == false)
> 463 + return NULL;
>
> Just use ! + nullptr
>
> 481 +void FilterMultiRangeWidget::RecvMouseLeave(int x, int y, unsigned
> long button_flags, unsigned long key_flags)
> 482 +{
> 483 +}
>
> Empty function?
>
> I'll continue the review later.

All done.

Revision history for this message
Andrea Azzarone (azzar1) wrote :

Code looks good. All tests pass.

review: Approve
Revision history for this message
Andrea Azzarone (azzar1) wrote :

1023 + // filter_widget_->EmitMouseDragSignal(center_button3.x, center_button3.y, center_button4.x - center_button3.x, center_button4.y - center_button3.y, NUX_STATE_BUTTON1_DOWN, 0);
1024 + // unity::testing::ExpectFilterRange(filter, 1, 3);
1025 +
1026 + // filter_widget_->EmitMouseUpSignal(center_button3.x, center_button3.y, NUX_EVENT_BUTTON1_UP, 0);
1027 + // EXPECT_EQ(filter_widget_->dragging_, false);
1028 + // unity::testing::ExpectFilterRange(filter, 1, 3);

Too fast... Please remove these commented lines.

review: Needs Fixing
Revision history for this message
Andrea Azzarone (azzar1) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'UnityCore/MultiRangeFilter.cpp'
2--- UnityCore/MultiRangeFilter.cpp 2012-10-29 09:34:54 +0000
3+++ UnityCore/MultiRangeFilter.cpp 2013-01-30 10:36:20 +0000
4@@ -88,64 +88,54 @@
5 return;
6
7 int position = PositionOfId(id);
8-
9+ if (position < 0)
10+ return;
11+
12+ bool activate = is_active;
13+ int position_above = position+1;
14+ int position_below = position-1;
15+ int filter_option_size = options_.size();
16+
17+ ignore_changes_ = true;
18+
19+ // when activating a option, need to make sure we only have continuous ajacent options enabled relative to the enabled option.
20 if (is_active)
21 {
22- if (left_pos_ == -1 && right_pos_ == -1)
23- {
24- left_pos_ = position;
25- right_pos_ = position;
26- }
27- else if (left_pos_ > position)
28- {
29- left_pos_ = position;
30- }
31- else if (right_pos_ < position)
32- {
33- right_pos_ = position;
34+ while(position_below >= 0)
35+ {
36+ FilterOption::Ptr const& filter_option = options_[position_below--];
37+
38+ activate &= filter_option->active;
39+ filter_option->active = activate;
40+ }
41+
42+ activate = is_active;
43+ while(position_above < filter_option_size)
44+ {
45+ FilterOption::Ptr const& filter_option = options_[position_above++];
46+
47+ activate &= filter_option->active;
48+ filter_option->active = activate;
49 }
50 }
51 else
52 {
53- // Reset if the one and only block is deactivated
54- if (position == right_pos_ && position == left_pos_)
55- {
56- left_pos_ = -1;
57- right_pos_ = -1;
58- }
59- // If the deactivated block is on either end, remove it
60- else if (position == right_pos_)
61- {
62- right_pos_ = position - 1;
63- }
64- else if (position == left_pos_)
65- {
66- left_pos_ = position + 1;
67- }
68- // It's in the middle of the range, see which side to shorten
69- else if (position < (left_pos_ + ((right_pos_ - left_pos_)/2.0f)))
70- {
71- left_pos_ = position;
72- }
73- else
74- {
75- right_pos_ = position;
76- }
77- }
78-
79- ignore_changes_ = true;
80- int i = 0;
81- for(auto option: options_)
82- {
83- if (i < left_pos_)
84- option->active = false;
85- else if (i <= right_pos_)
86- option->active = true;
87- else
88- option->active = false;
89-
90- i++;
91- }
92+ // otherwise just ensure there is a single continuous option range
93+ bool active_found = false, inactive_found = false;
94+ for (FilterOption::Ptr const& option : options_)
95+ {
96+ if (inactive_found)
97+ option->active = false;
98+ else
99+ {
100+ if (option->active)
101+ active_found = true;
102+ else if (active_found)
103+ inactive_found = true;
104+ }
105+ }
106+ }
107+
108 ignore_changes_ = false;
109
110 UpdateState();
111
112=== modified file 'dash/FilterAllButton.cpp'
113--- dash/FilterAllButton.cpp 2012-11-27 23:16:06 +0000
114+++ dash/FilterAllButton.cpp 2013-01-30 10:36:20 +0000
115@@ -32,6 +32,8 @@
116 namespace dash
117 {
118
119+NUX_IMPLEMENT_OBJECT_TYPE(FilterAllButton);
120+
121 FilterAllButton::FilterAllButton(NUX_FILE_LINE_DECL)
122 : FilterBasicButton(_("All"), NUX_FILE_LINE_PARAM)
123 {
124
125=== modified file 'dash/FilterAllButton.h'
126--- dash/FilterAllButton.h 2012-05-06 23:48:38 +0000
127+++ dash/FilterAllButton.h 2013-01-30 10:36:20 +0000
128@@ -35,6 +35,7 @@
129
130 class FilterAllButton : public FilterBasicButton
131 {
132+ NUX_DECLARE_OBJECT_TYPE(FilterAllButton, FilterBasicButton);
133 public:
134 FilterAllButton(NUX_FILE_LINE_PROTO);
135 ~FilterAllButton();
136
137=== modified file 'dash/FilterBasicButton.cpp'
138--- dash/FilterBasicButton.cpp 2012-11-27 23:16:06 +0000
139+++ dash/FilterBasicButton.cpp 2013-01-30 10:36:20 +0000
140@@ -33,6 +33,8 @@
141 {
142 namespace dash
143 {
144+
145+NUX_IMPLEMENT_OBJECT_TYPE(FilterBasicButton);
146
147 FilterBasicButton::FilterBasicButton(nux::TextureArea* image, NUX_FILE_LINE_DECL)
148 : nux::ToggleButton(image, NUX_FILE_LINE_PARAM)
149
150=== modified file 'dash/FilterBasicButton.h'
151--- dash/FilterBasicButton.h 2012-09-06 05:36:00 +0000
152+++ dash/FilterBasicButton.h 2013-01-30 10:36:20 +0000
153@@ -33,6 +33,7 @@
154
155 class FilterBasicButton : public nux::ToggleButton
156 {
157+ NUX_DECLARE_OBJECT_TYPE(FilterBasicButton, nux::ToggleButton);
158 public:
159 FilterBasicButton(nux::TextureArea* image, NUX_FILE_LINE_PROTO);
160 FilterBasicButton(std::string const& label, NUX_FILE_LINE_PROTO);
161
162=== modified file 'dash/FilterFactory.cpp'
163--- dash/FilterFactory.cpp 2012-10-29 09:34:54 +0000
164+++ dash/FilterFactory.cpp 2013-01-30 10:36:20 +0000
165@@ -64,7 +64,7 @@
166 }
167 else if (filter_type == renderer_type_multirange)
168 {
169- widget = new FilterMultiRange(NUX_TRACKER_LOCATION);
170+ widget = new FilterMultiRangeWidget(NUX_TRACKER_LOCATION);
171 }
172 else if (filter_type == renderer_type_radio_options)
173 {
174
175=== modified file 'dash/FilterGenreButton.cpp'
176--- dash/FilterGenreButton.cpp 2012-05-06 23:48:38 +0000
177+++ dash/FilterGenreButton.cpp 2013-01-30 10:36:20 +0000
178@@ -26,6 +26,8 @@
179 namespace dash
180 {
181
182+NUX_IMPLEMENT_OBJECT_TYPE(FilterGenreButton);
183+
184 FilterGenreButton::FilterGenreButton(std::string const& label, NUX_FILE_LINE_DECL)
185 : FilterBasicButton(label, NUX_FILE_LINE_PARAM)
186 {
187
188=== modified file 'dash/FilterGenreButton.h'
189--- dash/FilterGenreButton.h 2012-05-06 23:48:38 +0000
190+++ dash/FilterGenreButton.h 2013-01-30 10:36:20 +0000
191@@ -35,6 +35,7 @@
192
193 class FilterGenreButton : public FilterBasicButton
194 {
195+ NUX_DECLARE_OBJECT_TYPE(FilterGenreButton, FilterBasicButton);
196 public:
197 FilterGenreButton(std::string const& label, NUX_FILE_LINE_PROTO);
198 FilterGenreButton(NUX_FILE_LINE_PROTO);
199
200=== modified file 'dash/FilterMultiRangeButton.cpp'
201--- dash/FilterMultiRangeButton.cpp 2012-12-13 14:53:32 +0000
202+++ dash/FilterMultiRangeButton.cpp 2013-01-30 10:36:20 +0000
203@@ -21,6 +21,7 @@
204 #include "config.h"
205
206 #include <Nux/Nux.h>
207+#include <Nux/Layout.h>
208
209 #include "unity-shared/DashStyle.h"
210 #include "FilterMultiRangeButton.h"
211@@ -30,16 +31,19 @@
212 namespace dash
213 {
214
215-FilterMultiRangeButton::FilterMultiRangeButton(std::string const& label, NUX_FILE_LINE_DECL)
216- : nux::ToggleButton(label, NUX_FILE_LINE_PARAM)
217- , has_arrow_(MultiRangeArrow::NONE)
218- , side_(MultiRangeSide::CENTER)
219+namespace
220 {
221- Init();
222+const int kFontSizePx = 10;
223+
224+const int kLayoutPadLeftRight = 4;
225+const int kLayoutPadtopBottom = 2;
226 }
227
228+NUX_IMPLEMENT_OBJECT_TYPE(FilterMultiRangeButton);
229+
230 FilterMultiRangeButton::FilterMultiRangeButton(NUX_FILE_LINE_DECL)
231 : nux::ToggleButton(NUX_FILE_LINE_PARAM)
232+ , theme_init_(false)
233 , has_arrow_(MultiRangeArrow::NONE)
234 , side_(MultiRangeSide::CENTER)
235 {
236@@ -53,8 +57,9 @@
237 void FilterMultiRangeButton::Init()
238 {
239 InitTheme();
240+ // Controlled by parent widget
241 SetAcceptKeyNavFocusOnMouseDown(false);
242- SetAcceptKeyNavFocusOnMouseEnter(true);
243+ SetAcceptKeyNavFocusOnMouseEnter(false);
244
245 state_change.connect(sigc::mem_fun(this, &FilterMultiRangeButton::OnActivated));
246 key_nav_focus_change.connect([&](nux::Area*, bool, nux::KeyNavDirection) { QueueDraw(); });
247@@ -105,23 +110,22 @@
248 {
249 long ret = nux::ToggleButton::ComputeContentSize();
250 nux::Geometry const& geo = GetGeometry();
251- if (cached_geometry_ != geo)
252+ if (theme_init_ && cached_geometry_ != geo)
253 {
254 cached_geometry_ = geo;
255
256 std::vector<MultiRangeSide> sides = {MultiRangeSide::LEFT, MultiRangeSide::RIGHT, MultiRangeSide::CENTER};
257 std::vector<MultiRangeArrow> arrows = {MultiRangeArrow::LEFT, MultiRangeArrow::RIGHT, MultiRangeArrow::BOTH, MultiRangeArrow::NONE};
258
259- for (auto arrow : arrows)
260+ auto func_invalidate = [geo](std::pair<const MapKey, NuxCairoPtr>& pair)
261 {
262- for (auto side : sides)
263- {
264- prelight_[MapKey(arrow, side)]->Invalidate(geo);
265- active_[MapKey(arrow, side)]->Invalidate(geo);
266- normal_[MapKey(arrow, side)]->Invalidate(geo);
267- focus_[MapKey(arrow, side)]->Invalidate(geo);
268- }
269- }
270+ pair.second->Invalidate(geo);
271+ };
272+
273+ for_each (prelight_.begin(), prelight_.end(), func_invalidate);
274+ for_each (active_.begin(), active_.end(), func_invalidate);
275+ for_each (normal_.begin(), normal_.end(), func_invalidate);
276+ for_each (focus_.begin(), focus_.end(), func_invalidate);
277 }
278
279 return ret;
280@@ -149,6 +153,7 @@
281 }
282
283 SetMinimumHeight(dash::Style::Instance().GetFilterButtonHeight() + 3);
284+ theme_init_ = true;
285 }
286
287 void FilterMultiRangeButton::RedrawTheme(nux::Geometry const& geom,
288@@ -182,7 +187,7 @@
289 else
290 segment = Segment::RIGHT;
291
292- Style::Instance().MultiRangeSegment(cr, faked_state, name, arrow, segment);
293+ Style::Instance().MultiRangeSegment(cr, faked_state, name, kFontSizePx, arrow, segment);
294 NeedRedraw();
295 }
296
297
298=== modified file 'dash/FilterMultiRangeButton.h'
299--- dash/FilterMultiRangeButton.h 2012-05-06 23:48:38 +0000
300+++ dash/FilterMultiRangeButton.h 2013-01-30 10:36:20 +0000
301@@ -52,8 +52,8 @@
302
303 class FilterMultiRangeButton : public nux::ToggleButton
304 {
305+ NUX_DECLARE_OBJECT_TYPE(FilterMultiRangeButton, nux::ToggleButton);
306 public:
307- FilterMultiRangeButton (std::string const& label, NUX_FILE_LINE_PROTO);
308 FilterMultiRangeButton (NUX_FILE_LINE_PROTO);
309 ~FilterMultiRangeButton();
310
311@@ -94,6 +94,7 @@
312 std::map<MapKey, NuxCairoPtr> focus_;
313 std::map<MapKey, NuxCairoPtr> normal_;
314 std::map<MapKey, NuxCairoPtr> prelight_;
315+ bool theme_init_;
316
317 nux::Geometry cached_geometry_;
318 MultiRangeArrow has_arrow_;
319
320=== modified file 'dash/FilterMultiRangeWidget.cpp'
321--- dash/FilterMultiRangeWidget.cpp 2012-11-27 23:16:06 +0000
322+++ dash/FilterMultiRangeWidget.cpp 2013-01-30 10:36:20 +0000
323@@ -37,10 +37,11 @@
324 namespace dash
325 {
326
327-NUX_IMPLEMENT_OBJECT_TYPE(FilterMultiRange);
328+NUX_IMPLEMENT_OBJECT_TYPE(FilterMultiRangeWidget);
329
330-FilterMultiRange::FilterMultiRange(NUX_FILE_LINE_DECL)
331+FilterMultiRangeWidget::FilterMultiRangeWidget(NUX_FILE_LINE_DECL)
332 : FilterExpanderLabel(_("Multi-range"), NUX_FILE_LINE_PARAM)
333+ , dragging_(false)
334 {
335 InitTheme();
336
337@@ -59,21 +60,30 @@
338 SetRightHandView(all_button_);
339 SetContents(layout_);
340 OnActiveChanged(false);
341-}
342-
343-FilterMultiRange::~FilterMultiRange()
344-{
345-}
346-
347-void FilterMultiRange::SetFilter(Filter::Ptr const& filter)
348-{
349+
350+ mouse_move.connect(sigc::mem_fun(this, &FilterMultiRangeWidget::RecvMouseMove));
351+ mouse_down.connect(sigc::mem_fun(this, &FilterMultiRangeWidget::RecvMouseDown));
352+ mouse_up.connect(sigc::mem_fun(this, &FilterMultiRangeWidget::RecvMouseUp));
353+
354+ mouse_drag.connect(sigc::mem_fun(this, &FilterMultiRangeWidget::RecvMouseDrag));
355+}
356+
357+void FilterMultiRangeWidget::SetFilter(Filter::Ptr const& filter)
358+{
359+ // Reset filter.
360+ layout_->Clear();
361+ buttons_.clear();
362+ mouse_down_button_.Release();
363+ mouse_down_left_active_button_.Release();
364+ mouse_down_right_active_button_.Release();
365+
366 filter_ = std::static_pointer_cast<MultiRangeFilter>(filter);
367
368 all_button_->SetFilter(filter_);
369 expanded = !filter_->collapsed();
370
371- filter_->option_added.connect(sigc::mem_fun(this, &FilterMultiRange::OnOptionAdded));
372- filter_->option_removed.connect(sigc::mem_fun(this, &FilterMultiRange::OnOptionRemoved));
373+ filter_->option_added.connect(sigc::mem_fun(this, &FilterMultiRangeWidget::OnOptionAdded));
374+ filter_->option_removed.connect(sigc::mem_fun(this, &FilterMultiRangeWidget::OnOptionRemoved));
375
376 // finally - make sure we are up-todate with our filter list
377 for (auto it : filter_->options())
378@@ -82,7 +92,7 @@
379 SetLabel(filter_->name);
380 }
381
382-void FilterMultiRange::OnActiveChanged(bool value)
383+void FilterMultiRangeWidget::OnActiveChanged(bool value)
384 {
385 // go through all the buttons, and set the state :(
386 int start = 2000;
387@@ -91,10 +101,11 @@
388 for (auto button : buttons_)
389 {
390 FilterOption::Ptr filter = button->GetFilter();
391- bool tmp_active = filter->active;
392- button->SetActive(tmp_active);
393 if (filter != nullptr)
394 {
395+ bool tmp_active = filter->active;
396+ button->SetActive(tmp_active);
397+
398 if (filter->active)
399 {
400 if (index < start)
401@@ -129,43 +140,249 @@
402 }
403 }
404
405-void FilterMultiRange::OnOptionAdded(FilterOption::Ptr const& new_filter)
406+void FilterMultiRangeWidget::OnOptionAdded(FilterOption::Ptr const& new_filter)
407 {
408- FilterMultiRangeButton* button = new FilterMultiRangeButton(NUX_TRACKER_LOCATION);
409+ FilterMultiRangeButtonPtr button(new FilterMultiRangeButton(NUX_TRACKER_LOCATION));
410 button->SetFilter(new_filter);
411- layout_->AddView(button);
412+ layout_->AddView(button.GetPointer());
413 buttons_.push_back(button);
414- new_filter->active.changed.connect(sigc::mem_fun(this, &FilterMultiRange::OnActiveChanged));
415+ new_filter->active.changed.connect(sigc::mem_fun(this, &FilterMultiRangeWidget::OnActiveChanged));
416 OnActiveChanged(false);
417
418+ QueueRelayout();
419 }
420
421-void FilterMultiRange::OnOptionRemoved(FilterOption::Ptr const& removed_filter)
422+void FilterMultiRangeWidget::OnOptionRemoved(FilterOption::Ptr const& removed_filter)
423 {
424 for (auto it=buttons_.begin() ; it != buttons_.end(); it++)
425 {
426 if ((*it)->GetFilter() == removed_filter)
427 {
428- layout_->RemoveChildObject(*it);
429+ layout_->RemoveChildObject(it->GetPointer());
430 buttons_.erase(it);
431 break;
432 }
433 }
434-
435 OnActiveChanged(false);
436+
437+ QueueRelayout();
438 }
439
440-std::string FilterMultiRange::GetFilterType()
441+std::string FilterMultiRangeWidget::GetFilterType()
442 {
443- return "FilterMultiRange";
444+ return "FilterMultiRangeWidget";
445 }
446
447-void FilterMultiRange::InitTheme()
448+void FilterMultiRangeWidget::InitTheme()
449 {
450 //FIXME - build theme here - store images, cache them, fun fun fun
451 }
452
453-void FilterMultiRange::ClearRedirectedRenderChildArea()
454+nux::Area* FilterMultiRangeWidget::FindAreaUnderMouse(const nux::Point& mouse_position, nux::NuxEventType event_type)
455+{
456+ bool mouse_inside = TestMousePointerInclusionFilterMouseWheel(mouse_position, event_type);
457+ if (!mouse_inside)
458+ return nullptr;
459+
460+ nux::Area* area = View::FindAreaUnderMouse(mouse_position, nux::NUX_MOUSE_MOVE);
461+ if (area && area->Type().IsDerivedFromType(FilterMultiRangeButton::StaticObjectType))
462+ {
463+ return this;
464+ }
465+
466+ return area;
467+}
468+
469+void FilterMultiRangeWidget::RecvMouseMove(int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags)
470+{
471+ nux::Geometry geo = GetAbsoluteGeometry();
472+ nux::Point abs_cursor(geo.x + x, geo.y + y);
473+ UpdateMouseFocus(abs_cursor);
474+}
475+
476+void FilterMultiRangeWidget::RecvMouseDown(int x, int y, unsigned long button_flags, unsigned long key_flags)
477+{
478+ mouse_down_button_.Release();
479+ mouse_down_left_active_button_.Release();
480+ mouse_down_right_active_button_.Release();
481+
482+ dragging_ = false;
483+ nux::Geometry geo = GetAbsoluteGeometry();
484+ nux::Point abs_cursor(geo.x + x, geo.y + y);
485+
486+ nux::Area* area = View::FindAreaUnderMouse(nux::Point(abs_cursor.x, abs_cursor.y), nux::NUX_MOUSE_PRESSED);
487+
488+ if (!area || !area->Type().IsDerivedFromType(FilterMultiRangeButton::StaticObjectType))
489+ return;
490+ mouse_down_button_ = static_cast<FilterMultiRangeButton*>(area);
491+
492+ // Cache the left/right selected buttons.
493+ FilterMultiRangeButtonPtr last_selected_button;
494+ for (FilterMultiRangeButtonPtr button : buttons_)
495+ {
496+ if (button->Active())
497+ {
498+ if (!mouse_down_left_active_button_.IsValid())
499+ mouse_down_left_active_button_ = button;
500+ last_selected_button = button;
501+ }
502+ }
503+ mouse_down_right_active_button_ = last_selected_button;
504+}
505+
506+void FilterMultiRangeWidget::RecvMouseUp(int x, int y, unsigned long button_flags, unsigned long key_flags)
507+{
508+ FilterMultiRangeButtonPtr mouse_down_button(mouse_down_button_);
509+ mouse_down_button_.Release();
510+
511+ if (dragging_)
512+ {
513+ dragging_ = false;
514+ return;
515+ }
516+
517+ nux::Geometry geo = GetAbsoluteGeometry();
518+ nux::Area* area = View::FindAreaUnderMouse(nux::Point(geo.x + x, geo.y + y), nux::NUX_MOUSE_RELEASED);
519+ if (!area || !area->Type().IsDerivedFromType(FilterMultiRangeButton::StaticObjectType))
520+ return;
521+
522+ FilterMultiRangeButtonPtr mouse_up_button;
523+ mouse_up_button = static_cast<FilterMultiRangeButton*>(area);
524+ if (mouse_up_button == mouse_down_button)
525+ Click(mouse_up_button);
526+}
527+
528+void FilterMultiRangeWidget::RecvMouseDrag(int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags)
529+{
530+ nux::Geometry geo = GetAbsoluteGeometry();
531+ nux::Point abs_cursor(geo.x + x, geo.y + y);
532+ UpdateMouseFocus(abs_cursor);
533+
534+ if (!CheckDrag())
535+ return;
536+
537+ nux::Area* area = View::FindAreaUnderMouse(nux::Point(abs_cursor.x, abs_cursor.y), nux::NUX_MOUSE_MOVE);
538+ if (!area || !area->Type().IsDerivedFromType(FilterMultiRangeButton::StaticObjectType))
539+ return;
540+
541+ FilterMultiRangeButtonPtr drag_over_button;
542+ drag_over_button = static_cast<FilterMultiRangeButton*>(area);
543+ if (!drag_over_button.IsValid())
544+ return;
545+ dragging_ = true;
546+
547+ nux::Geometry mouse_down_button_geometry = mouse_down_button_->GetAbsoluteGeometry();
548+
549+ nux::Geometry left_active_button_geometry = mouse_down_left_active_button_->GetAbsoluteGeometry();
550+ nux::Geometry right_active_button_geometry = mouse_down_right_active_button_->GetAbsoluteGeometry();
551+
552+ auto end = buttons_.end();
553+ int found_buttons = 0;
554+ for (auto iter = buttons_.begin(); iter != end; ++iter)
555+ {
556+ FilterMultiRangeButtonPtr button = *iter;
557+ bool activate = false;
558+
559+ // if we've dragged the left button, we want to activate everything between the "drag over button" and the "right button"
560+ if (mouse_down_button_ == mouse_down_left_active_button_ &&
561+ button == mouse_down_right_active_button_)
562+ {
563+ found_buttons++;
564+ activate = true;
565+ }
566+ // if we've dragged the right button, we want to activate everything between the "left button" and the "drag over button"
567+ else if (mouse_down_button_ == mouse_down_right_active_button_ &&
568+ button == mouse_down_left_active_button_)
569+ {
570+ found_buttons++;
571+ activate = true;
572+ }
573+
574+ if (button == drag_over_button)
575+ {
576+ found_buttons++;
577+ activate = true;
578+ }
579+
580+ if (activate || (found_buttons > 0 && found_buttons < 2))
581+ {
582+ button->Activate();
583+ }
584+ else
585+ {
586+ button->Deactivate();
587+ }
588+ }
589+}
590+
591+bool FilterMultiRangeWidget::CheckDrag()
592+{
593+ if (!mouse_down_button_)
594+ return false;
595+
596+ auto end = buttons_.end();
597+ bool between = false;
598+ bool active_found = false;
599+ for (auto iter = buttons_.begin(); iter != end; ++iter)
600+ {
601+ FilterMultiRangeButtonPtr button = *iter;
602+ if (button->Active())
603+ {
604+ active_found = true;
605+ if (button == mouse_down_button_)
606+ {
607+ between = true;
608+ }
609+ }
610+ else if (active_found)
611+ {
612+ active_found = false;
613+ break;
614+ }
615+ }
616+
617+ if (mouse_down_button_ != mouse_down_left_active_button_ && mouse_down_button_ != mouse_down_right_active_button_)
618+ {
619+ if (between)
620+ return false;
621+ mouse_down_left_active_button_ = mouse_down_button_;
622+ mouse_down_right_active_button_ = mouse_down_button_;
623+ }
624+
625+ return true;
626+}
627+
628+void FilterMultiRangeWidget::UpdateMouseFocus(nux::Point const& abs_cursor_position)
629+{
630+ nux::Area* area = View::FindAreaUnderMouse(nux::Point(abs_cursor_position.x, abs_cursor_position.y), nux::NUX_MOUSE_MOVE);
631+ if (!area || !area->Type().IsDerivedFromType(FilterMultiRangeButton::StaticObjectType))
632+ return;
633+
634+ nux::GetWindowCompositor().SetKeyFocusArea(static_cast<InputArea*>(area), nux::KEY_NAV_NONE);
635+}
636+
637+void FilterMultiRangeWidget::Click(FilterMultiRangeButtonPtr const& activated_button)
638+{
639+ bool current_activated = activated_button->Active();
640+ bool any_others_active = false;
641+
642+ for (FilterMultiRangeButtonPtr button : buttons_)
643+ {
644+ if (button != activated_button)
645+ {
646+ if (button->Active())
647+ any_others_active = true;
648+ button->Deactivate();
649+ }
650+ }
651+
652+ if (!any_others_active && current_activated)
653+ activated_button->Deactivate();
654+ else
655+ activated_button->Activate();
656+}
657+
658+void FilterMultiRangeWidget::ClearRedirectedRenderChildArea()
659 {
660 for (auto button : buttons_)
661 {
662
663=== modified file 'dash/FilterMultiRangeWidget.h'
664--- dash/FilterMultiRangeWidget.h 2012-11-27 23:16:06 +0000
665+++ dash/FilterMultiRangeWidget.h 2013-01-30 10:36:20 +0000
666@@ -39,12 +39,12 @@
667
668 class FilterMultiRangeButton;
669
670-class FilterMultiRange : public FilterExpanderLabel
671+class FilterMultiRangeWidget : public FilterExpanderLabel
672 {
673- NUX_DECLARE_OBJECT_TYPE(FilterMultiRange, FilterExpanderLabel);
674+ NUX_DECLARE_OBJECT_TYPE(FilterMultiRangeWidget, FilterExpanderLabel);
675+ typedef nux::ObjectPtr<FilterMultiRangeButton> FilterMultiRangeButtonPtr;
676 public:
677- FilterMultiRange(NUX_FILE_LINE_PROTO);
678- virtual ~FilterMultiRange();
679+ FilterMultiRangeWidget(NUX_FILE_LINE_PROTO);
680
681 void SetFilter(Filter::Ptr const& filter);
682 std::string GetFilterType();
683@@ -52,6 +52,13 @@
684 protected:
685 void InitTheme();
686
687+ nux::Area* FindAreaUnderMouse(const nux::Point& mouse_position, nux::NuxEventType event_type);
688+
689+ void RecvMouseMove(int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags);
690+ void RecvMouseUp(int x, int y, unsigned long button_flags, unsigned long key_flags);
691+ void RecvMouseDown(int x, int y, unsigned long button_flags, unsigned long key_flags);
692+ void RecvMouseDrag(int x, int y, int dx, int dy, unsigned long button_flags, unsigned long key_flags);
693+
694 void ClearRedirectedRenderChildArea();
695
696 private:
697@@ -60,11 +67,23 @@
698 void OnOptionRemoved(dash::FilterOption::Ptr const& removed_filter);
699 void OnActiveChanged(bool value);
700
701+ void UpdateMouseFocus(nux::Point const& abs_cursor_position);
702+ virtual void Click(FilterMultiRangeButtonPtr const& button);
703+
704+ bool CheckDrag();
705+
706 nux::HLayout* layout_;
707 FilterAllButton* all_button_;
708
709- std::vector<FilterMultiRangeButton*> buttons_;
710+ std::vector<FilterMultiRangeButtonPtr> buttons_;
711 MultiRangeFilter::Ptr filter_;
712+
713+ FilterMultiRangeButtonPtr mouse_down_button_;
714+ FilterMultiRangeButtonPtr mouse_down_left_active_button_;
715+ FilterMultiRangeButtonPtr mouse_down_right_active_button_;
716+ bool dragging_;
717+
718+ friend class TestFilterMultiRangeWidget;
719 };
720
721 } // unityshell dash
722
723=== modified file 'dash/FilterRatingsButton.cpp'
724--- dash/FilterRatingsButton.cpp 2012-12-13 14:53:32 +0000
725+++ dash/FilterRatingsButton.cpp 2013-01-30 10:36:20 +0000
726@@ -38,6 +38,9 @@
727 {
728 namespace dash
729 {
730+
731+NUX_IMPLEMENT_OBJECT_TYPE(FilterRatingsButton);
732+
733 FilterRatingsButton::FilterRatingsButton(NUX_FILE_LINE_DECL)
734 : nux::ToggleButton(NUX_FILE_LINE_PARAM)
735 , focused_star_(-1)
736
737=== modified file 'dash/FilterRatingsButton.h'
738--- dash/FilterRatingsButton.h 2012-05-06 23:48:38 +0000
739+++ dash/FilterRatingsButton.h 2013-01-30 10:36:20 +0000
740@@ -36,6 +36,7 @@
741
742 class FilterRatingsButton : public nux::ToggleButton
743 {
744+ NUX_DECLARE_OBJECT_TYPE(FilterRatingsButton, nux::ToggleButton);
745 public:
746 FilterRatingsButton(NUX_FILE_LINE_PROTO);
747 virtual ~FilterRatingsButton();
748
749=== modified file 'tests/CMakeLists.txt'
750--- tests/CMakeLists.txt 2013-01-29 23:05:50 +0000
751+++ tests/CMakeLists.txt 2013-01-30 10:36:20 +0000
752@@ -198,6 +198,7 @@
753 test_desktop_launcher_icon.cpp
754 test_device_launcher_section.cpp
755 test_edge_barrier_controller.cpp
756+ test_filter_widgets.cpp
757 test_hud_button.cpp
758 test_hud_controller.cpp
759 test_hud_launcher_icon.cpp
760
761=== added file 'tests/test_filter_multirange.h'
762--- tests/test_filter_multirange.h 1970-01-01 00:00:00 +0000
763+++ tests/test_filter_multirange.h 2013-01-30 10:36:20 +0000
764@@ -0,0 +1,75 @@
765+/*
766+ * Copyright 2012 Canonical Ltd.
767+ *
768+ * This program is free software: you can redistribute it and/or modify it
769+ * under the terms of the GNU Lesser General Public License version 3, as
770+ * published by the Free Software Foundation.
771+ *
772+ * This program is distributed in the hope that it will be useful, but
773+ * WITHOUT ANY WARRANTY; without even the implied warranties of
774+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
775+ * PURPOSE. See the applicable version of the GNU Lesser General Public
776+ * License for more details.
777+ *
778+ * You should have received a copy of both the GNU Lesser General Public
779+ * License version 3 along with this program. If not, see
780+ * <http://www.gnu.org/licenses/>
781+ *
782+ * Authored by: Nick Dedekind <nick.dedekind@canonical.com>
783+ *
784+ */
785+
786+#ifndef TEST_FILTER
787+#define TEST_FILTER
788+
789+#include <glib-object.h>
790+#include <gtest/gtest.h>
791+
792+#include "UnityCore/MultiRangeFilter.h"
793+
794+namespace unity
795+{
796+namespace testing
797+{
798+
799+GVariantBuilder* AddFilterHint(GVariantBuilder* builder, const char* name, GVariant* value)
800+{
801+ if (builder == NULL)
802+ builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
803+ g_variant_builder_add (builder, "{sv}", name, value);
804+ return builder;
805+}
806+
807+GVariant* AddFilterOptions(std::vector<bool> option_active)
808+{
809+ GVariantBuilder* builder;
810+ builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
811+
812+ int i = 0;
813+ for (auto iter = option_active.begin(), end = option_active.end(); iter != end; ++iter)
814+ {
815+ std::stringstream name;
816+ name << i++;
817+
818+ const char* pstr_name = name.str().c_str();
819+
820+ g_variant_builder_add (builder, "(sssb)", pstr_name, pstr_name, "", FALSE);
821+ }
822+ return g_variant_builder_end (builder);
823+}
824+
825+void ExpectFilterRange(unity::dash::MultiRangeFilter::Ptr const& filter, int first, int last)
826+{
827+ int i = 0;
828+ for (auto option : filter->options())
829+ {
830+ bool should_be_active = i >= first && i <= last;
831+ EXPECT_EQ(option->active, should_be_active);
832+ i++;
833+ }
834+}
835+
836+} // namespace testing
837+} // namespace unity
838+
839+#endif
840\ No newline at end of file
841
842=== added file 'tests/test_filter_widgets.cpp'
843--- tests/test_filter_widgets.cpp 1970-01-01 00:00:00 +0000
844+++ tests/test_filter_widgets.cpp 2013-01-30 10:36:20 +0000
845@@ -0,0 +1,187 @@
846+/*
847+ * Copyright 2012 Canonical Ltd.
848+ *
849+ * This program is free software: you can redistribute it and/or modify it
850+ * under the terms of the GNU Lesser General Public License version 3, as
851+ * published by the Free Software Foundation.
852+ *
853+ * This program is distributed in the hope that it will be useful, but
854+ * WITHOUT ANY WARRANTY; without even the implied warranties of
855+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
856+ * PURPOSE. See the applicable version of the GNU Lesser General Public
857+ * License for more details.
858+ *
859+ * You should have received a copy of both the GNU Lesser General Public
860+ * License version 3 along with this program. If not, see
861+ * <http://www.gnu.org/licenses/>
862+ *
863+ * Authored by: Nick Dedekind <nick.dedekind@canonical.com>
864+ *
865+ */
866+
867+#include <list>
868+
869+#include <gtest/gtest.h>
870+#include "test_filter_multirange.h"
871+#include "unity-shared/DashStyle.h"
872+#include "unity-shared/UnitySettings.h"
873+
874+#include <Nux/Nux.h>
875+#include <NuxGraphics/Events.h>
876+#include <NuxCore/ObjectPtr.h>
877+
878+#include <UnityCore/GLibWrapper.h>
879+#include "UnityCore/MultiRangeFilter.h"
880+#include "dash/FilterMultiRangeWidget.h"
881+#include "dash/FilterMultiRangeButton.h"
882+
883+using namespace unity;
884+
885+namespace unity
886+{
887+namespace dash
888+{
889+
890+class TestFilterMultiRangeWidget : public ::testing::Test
891+{
892+public:
893+ TestFilterMultiRangeWidget()
894+ : model_(dee_sequence_model_new())
895+ , filter_widget_(new MockFilterMultiRangeWidget())
896+ {
897+ dee_model_set_schema(model_, "s", "s", "s", "s", "a{sv}", "b", "b", "b", NULL);
898+ }
899+
900+ void SetFilter(MultiRangeFilter::Ptr const& filter)
901+ {
902+ filter_widget_->SetFilter(filter);
903+
904+ filter_widget_->ComputeContentSize();
905+ filter_widget_->ComputeContentPosition(0,0);
906+ }
907+
908+ class MockFilterMultiRangeWidget : public FilterMultiRangeWidget
909+ {
910+ public:
911+ MockFilterMultiRangeWidget()
912+ : clicked_(false)
913+ {
914+ }
915+
916+ void ResetState()
917+ {
918+ clicked_ = false;
919+ }
920+
921+ bool clicked_;
922+
923+ using FilterMultiRangeWidget::all_button_;
924+ using FilterMultiRangeWidget::buttons_;
925+ using FilterMultiRangeWidget::dragging_;
926+
927+ using InputArea::EmitMouseDownSignal;
928+ using InputArea::EmitMouseUpSignal;
929+ using InputArea::EmitMouseDragSignal;
930+
931+ using FilterMultiRangeWidget::mouse_down_button_;
932+
933+ protected:
934+
935+ virtual void Click(FilterMultiRangeButtonPtr const& button)
936+ {
937+ clicked_ = true;
938+ FilterMultiRangeWidget::Click(button);
939+ }
940+ };
941+
942+
943+ static DeeModelIter* AddMultiRangeFilterOptions(glib::Object<DeeModel> const& model)
944+ {
945+ std::vector<bool> option_active(6, false);
946+ GVariantBuilder* builder = nullptr;
947+ builder = unity::testing::AddFilterHint(builder, "options", unity::testing::AddFilterOptions(option_active));
948+ GVariant* hints = g_variant_builder_end(builder);
949+
950+ return dee_model_append(model,
951+ "genre",
952+ "Genre",
953+ "gtk-apply",
954+ "genre",
955+ hints,
956+ TRUE,
957+ FALSE, // collapsed
958+ FALSE);
959+ }
960+
961+ Settings unity_settings_;
962+ dash::Style dash_style_;
963+
964+ glib::Object<DeeModel> model_;
965+ nux::ObjectPtr<MockFilterMultiRangeWidget> filter_widget_;
966+};
967+
968+TEST_F(TestFilterMultiRangeWidget, TestConstruction)
969+{
970+ MultiRangeFilter::Ptr filter(new MultiRangeFilter(model_, AddMultiRangeFilterOptions(model_)));
971+ SetFilter(filter);
972+
973+ ASSERT_EQ(filter_widget_->buttons_.size(), 6);
974+}
975+
976+TEST_F(TestFilterMultiRangeWidget, TestClick)
977+{
978+ MultiRangeFilter::Ptr filter(new MultiRangeFilter(model_, AddMultiRangeFilterOptions(model_)));
979+ SetFilter(filter);
980+
981+ FilterMultiRangeWidget::FilterMultiRangeButtonPtr filter_button = filter_widget_->buttons_[1];
982+ nux::Geometry geo_button = filter_button->GetAbsoluteGeometry();
983+ nux::Point center_button1(geo_button.x + geo_button.width/2, geo_button.y + geo_button.height/2);
984+
985+ filter_widget_->EmitMouseDownSignal(center_button1.x, center_button1.y, NUX_STATE_BUTTON1_DOWN|NUX_EVENT_BUTTON1_DOWN, 0);
986+ ASSERT_NE(filter_widget_->mouse_down_button_, nullptr);
987+
988+ filter_widget_->EmitMouseUpSignal(center_button1.y, center_button1.y, NUX_EVENT_BUTTON1_UP, 0);
989+ ASSERT_EQ(filter_widget_->mouse_down_button_, nullptr);
990+
991+ EXPECT_EQ(filter_widget_->clicked_, true);
992+ EXPECT_EQ(filter_button->Active(), true);
993+ EXPECT_EQ(filter->options()[1]->active, true);
994+}
995+
996+TEST_F(TestFilterMultiRangeWidget, TestDrag)
997+{
998+ MultiRangeFilter::Ptr filter(new MultiRangeFilter(model_, AddMultiRangeFilterOptions(model_)));
999+ SetFilter(filter);
1000+
1001+ // Genre "1"
1002+ FilterMultiRangeWidget::FilterMultiRangeButtonPtr filter_button1 = filter_widget_->buttons_[1];
1003+ nux::Geometry geo_button1 = filter_button1->GetAbsoluteGeometry();
1004+ nux::Point center_button1(geo_button1.x + geo_button1.width/2, geo_button1.y + geo_button1.height/2);
1005+
1006+ // Genre "3"
1007+ FilterMultiRangeWidget::FilterMultiRangeButtonPtr filter_button3 = filter_widget_->buttons_[3];
1008+ nux::Geometry geo_button3 = filter_button3->GetAbsoluteGeometry();
1009+ nux::Point center_button3(geo_button3.x + geo_button3.width/2, geo_button3.y + geo_button3.height/2);
1010+
1011+ // Genre "4"
1012+ FilterMultiRangeWidget::FilterMultiRangeButtonPtr filter_button4 = filter_widget_->buttons_[4];
1013+ nux::Geometry geo_button4 = filter_button4->GetAbsoluteGeometry();
1014+ nux::Point center_button4(geo_button4.x + geo_button4.width/2, geo_button4.y + geo_button4.height/2);
1015+
1016+ filter_widget_->EmitMouseDownSignal(center_button1.x, center_button1.y, NUX_STATE_BUTTON1_DOWN|NUX_EVENT_BUTTON1_DOWN, 0);
1017+
1018+ filter_widget_->EmitMouseDragSignal(center_button4.x, center_button4.y, center_button4.x - center_button1.x, center_button4.y - center_button1.y, NUX_STATE_BUTTON1_DOWN, 0);
1019+ EXPECT_EQ(filter_widget_->dragging_, true);
1020+ unity::testing::ExpectFilterRange(filter, 1, 4);
1021+
1022+
1023+ // filter_widget_->EmitMouseDragSignal(center_button3.x, center_button3.y, center_button4.x - center_button3.x, center_button4.y - center_button3.y, NUX_STATE_BUTTON1_DOWN, 0);
1024+ // unity::testing::ExpectFilterRange(filter, 1, 3);
1025+
1026+ // filter_widget_->EmitMouseUpSignal(center_button3.x, center_button3.y, NUX_EVENT_BUTTON1_UP, 0);
1027+ // EXPECT_EQ(filter_widget_->dragging_, false);
1028+ // unity::testing::ExpectFilterRange(filter, 1, 3);
1029+}
1030+
1031+} // namespace dash
1032+} // namespace unity
1033
1034=== modified file 'tests/test_lens.cpp'
1035--- tests/test_lens.cpp 2012-11-14 08:57:56 +0000
1036+++ tests/test_lens.cpp 2013-01-30 10:36:20 +0000
1037@@ -576,27 +576,22 @@
1038 EXPECT_FALSE (options[3]->active);
1039
1040 options[0]->active = true;
1041+ options[1]->active = true;
1042+ EXPECT_TRUE (filter->filtering);
1043+ EXPECT_TRUE (options[0]->active);
1044+ EXPECT_TRUE (options[1]->active);
1045+
1046 options[3]->active = true;
1047 EXPECT_TRUE (filter->filtering);
1048- EXPECT_TRUE (options[0]->active);
1049- EXPECT_TRUE (options[1]->active);
1050- EXPECT_TRUE (options[2]->active);
1051- EXPECT_TRUE (options[3]->active);
1052-
1053- options[0]->active = true;
1054- options[2]->active = false;
1055- EXPECT_TRUE (filter->filtering);
1056- EXPECT_TRUE (options[0]->active);
1057- EXPECT_TRUE (options[1]->active);
1058- EXPECT_TRUE (options[2]->active);
1059- EXPECT_FALSE (options[3]->active);
1060-
1061- options[0]->active = false;
1062- EXPECT_TRUE (filter->filtering);
1063 EXPECT_FALSE (options[0]->active);
1064- EXPECT_TRUE (options[1]->active);
1065+ EXPECT_FALSE (options[1]->active);
1066+ EXPECT_FALSE (options[2]->active);
1067+ EXPECT_TRUE (options[3]->active);
1068+
1069+ options[2]->active = true;
1070+ EXPECT_TRUE (filter->filtering);
1071 EXPECT_TRUE (options[2]->active);
1072- EXPECT_FALSE (options[3]->active);
1073+ EXPECT_TRUE (options[3]->active);
1074
1075 filter->Clear();
1076 EXPECT_FALSE (filter->filtering);
1077
1078=== modified file 'unity-shared/DashStyle.cpp'
1079--- unity-shared/DashStyle.cpp 2013-01-09 14:57:08 +0000
1080+++ unity-shared/DashStyle.cpp 2013-01-30 10:36:20 +0000
1081@@ -60,6 +60,9 @@
1082
1083 const int STATES = 5;
1084
1085+const double BUTTON_CORNER_RADIUS = 7.0;
1086+
1087+
1088 // These cairo overrides may also be reused somewhere...
1089 void cairo_set_source_rgba(cairo_t* cr, nux::Color const& color)
1090 {
1091@@ -147,6 +150,15 @@
1092 double cornerRadius,
1093 double width,
1094 double height,
1095+ Segment segment);
1096+
1097+ void RoundedRectSegmentBorder(cairo_t* cr,
1098+ double aspect,
1099+ double x,
1100+ double y,
1101+ double cornerRadius,
1102+ double width,
1103+ double height,
1104 Segment segment,
1105 Arrow arrow,
1106 nux::ButtonVisualState state);
1107@@ -963,6 +975,109 @@
1108 double cornerRadius,
1109 double width,
1110 double height,
1111+ Segment segment)
1112+{
1113+ double radius = cornerRadius / aspect;
1114+ bool odd = true;
1115+
1116+ odd = cairo_get_line_width (cr) == 2.0 ? false : true;
1117+
1118+ switch (segment)
1119+ {
1120+ case Segment::LEFT:
1121+ // top-left, right of the corner
1122+ cairo_move_to(cr, _align(x + radius, odd), _align(y, odd));
1123+
1124+ // top-right
1125+ cairo_line_to(cr, _align(x + width, odd), _align(y, odd));
1126+
1127+ // bottom-right
1128+ cairo_line_to(cr, _align(x + width, odd), _align(y + height, odd));
1129+
1130+ // bottom-left, right of the corner
1131+ cairo_line_to(cr, _align(x + radius, odd), _align(y + height, odd));
1132+
1133+ // bottom-left, above the corner
1134+ cairo_arc(cr,
1135+ _align(x + radius, odd),
1136+ _align(y + height - radius, odd),
1137+ radius,
1138+ 90.0f * G_PI / 180.0f,
1139+ 180.0f * G_PI / 180.0f);
1140+
1141+ // left, right of the corner
1142+ cairo_line_to(cr, _align(x, odd), _align(y + radius, odd));
1143+
1144+ // top-left, right of the corner
1145+ cairo_arc(cr,
1146+ _align(x + radius, odd),
1147+ _align(y + radius, odd),
1148+ radius,
1149+ 180.0f * G_PI / 180.0f,
1150+ 270.0f * G_PI / 180.0f);
1151+
1152+ break;
1153+
1154+ case Segment::MIDDLE:
1155+ // top-left
1156+ cairo_move_to(cr, _align(x, odd), _align(y, odd));
1157+
1158+ // top-right
1159+ cairo_line_to(cr, _align(x + width, odd), _align(y, odd));
1160+
1161+ // bottom-right
1162+ cairo_line_to(cr, _align(x + width, odd), _align(y + height, odd));
1163+
1164+ // bottom-left
1165+ cairo_line_to(cr, _align(x, odd), _align(y + height, odd));
1166+
1167+ // back to top-left
1168+ cairo_close_path(cr);
1169+ break;
1170+
1171+ case Segment::RIGHT:
1172+ // top-left, right of the corner
1173+ cairo_move_to(cr, _align(x, odd), _align(y, odd));
1174+
1175+ // top-right, left of the corner
1176+ cairo_line_to(cr, _align(x + width - radius, odd), _align(y, odd));
1177+
1178+ // top-right, below the corner
1179+ cairo_arc(cr,
1180+ _align(x + width - radius, odd),
1181+ _align(y + radius, odd),
1182+ radius,
1183+ -90.0f * G_PI / 180.0f,
1184+ 0.0f * G_PI / 180.0f);
1185+
1186+ // bottom-right, above the corner
1187+ cairo_line_to(cr, _align(x + width, odd), _align(y + height - radius, odd));
1188+
1189+ // bottom-right, left of the corner
1190+ cairo_arc(cr,
1191+ _align(x + width - radius, odd),
1192+ _align(y + height - radius, odd),
1193+ radius,
1194+ 0.0f * G_PI / 180.0f,
1195+ 90.0f * G_PI / 180.0f);
1196+
1197+ // bottom-left
1198+ cairo_line_to(cr, _align(x, odd), _align(y + height, odd));
1199+
1200+ // back to top-left
1201+ cairo_close_path(cr);
1202+ break;
1203+ }
1204+}
1205+
1206+
1207+void Style::Impl::RoundedRectSegmentBorder(cairo_t* cr,
1208+ double aspect,
1209+ double x,
1210+ double y,
1211+ double cornerRadius,
1212+ double width,
1213+ double height,
1214 Segment segment,
1215 Arrow arrow,
1216 nux::ButtonVisualState state)
1217@@ -982,24 +1097,29 @@
1218 // top-right
1219 cairo_line_to(cr, _align(x + width, odd), _align(y, odd));
1220
1221- if (arrow == Arrow::RIGHT && state == nux::VISUAL_STATE_PRESSED)
1222- {
1223+ if (arrow == Arrow::RIGHT || arrow == Arrow::BOTH)
1224+ {
1225 cairo_line_to(cr, _align(x + width, odd), _align(y + height / 2.0 - arrow_h, odd));
1226 cairo_line_to(cr, _align(x + width - arrow_w, odd), _align(y + height / 2.0, odd));
1227 cairo_line_to(cr, _align(x + width, odd), _align(y + height / 2.0 + arrow_h, odd));
1228- }
1229-
1230- // bottom-right
1231- cairo_line_to(cr, _align(x + width, odd), _align(y + height, odd));
1232+
1233+ // bottom-right
1234+ cairo_line_to(cr, _align(x + width, odd), _align(y + height, odd));
1235+ }
1236+ else
1237+ {
1238+ // bottom-right
1239+ cairo_move_to(cr, _align(x + width, odd), _align(y + height, odd));
1240+ }
1241
1242 // bottom-left, right of the corner
1243 cairo_line_to(cr, _align(x + radius, odd), _align(y + height, odd));
1244
1245 // bottom-left, above the corner
1246 cairo_arc(cr,
1247- _align(x, odd) + _align(radius, odd),
1248- _align(y + height, odd) - _align(radius, odd),
1249- _align(radius, odd),
1250+ _align(x + radius, odd),
1251+ _align(y + height - radius, odd),
1252+ radius,
1253 90.0f * G_PI / 180.0f,
1254 180.0f * G_PI / 180.0f);
1255
1256@@ -1008,9 +1128,9 @@
1257
1258 // top-left, right of the corner
1259 cairo_arc(cr,
1260- _align(x, odd) + _align(radius, odd),
1261- _align(y, odd) + _align(radius, odd),
1262- _align(radius, odd),
1263+ _align(x + radius, odd),
1264+ _align(y + radius, odd),
1265+ radius,
1266 180.0f * G_PI / 180.0f,
1267 270.0f * G_PI / 180.0f);
1268
1269@@ -1024,27 +1144,33 @@
1270 cairo_line_to(cr, _align(x + width, odd), _align(y, odd));
1271
1272 if ((arrow == Arrow::RIGHT || arrow == Arrow::BOTH) && state == nux::VISUAL_STATE_PRESSED)
1273- {
1274+ {
1275 cairo_line_to(cr, _align(x + width, odd), _align(y + height / 2.0 - arrow_h, odd));
1276 cairo_line_to(cr, _align(x + width - arrow_w, odd), _align(y + height / 2.0, odd));
1277 cairo_line_to(cr, _align(x + width, odd), _align(y + height / 2.0 + arrow_h, odd));
1278- }
1279-
1280- // bottom-right
1281- cairo_line_to(cr, _align(x + width, odd), _align(y + height, odd));
1282+
1283+ // bottom-right
1284+ cairo_line_to(cr, _align(x + width, odd), _align(y + height, odd));
1285+ }
1286+ else
1287+ {
1288+ // bottom-right
1289+ cairo_move_to(cr, _align(x + width, odd), _align(y + height, odd));
1290+ }
1291
1292 // bottom-left
1293 cairo_line_to(cr, _align(x, odd), _align(y + height, odd));
1294
1295 if ((arrow == Arrow::LEFT || arrow == Arrow::BOTH) && state == nux::VISUAL_STATE_PRESSED)
1296- {
1297+ {
1298 cairo_line_to(cr, _align(x, odd), _align(y + height / 2.0 + arrow_h, odd));
1299 cairo_line_to(cr, _align(x + arrow_w, odd), _align(y + height / 2.0, odd));
1300 cairo_line_to(cr, _align(x, odd), _align(y + height / 2.0 - arrow_h, odd));
1301- }
1302-
1303- // back to top-left
1304- cairo_close_path(cr);
1305+
1306+ // top-left
1307+ cairo_line_to(cr, _align(x, odd), _align(y, odd));
1308+ }
1309+
1310 break;
1311
1312 case Segment::RIGHT:
1313@@ -1056,9 +1182,9 @@
1314
1315 // top-right, below the corner
1316 cairo_arc(cr,
1317- _align(x + width, odd) - _align(radius, odd),
1318- _align(y, odd) + _align(radius, odd),
1319- _align(radius, odd),
1320+ _align(x + width - radius, odd),
1321+ _align(y + radius, odd),
1322+ radius,
1323 -90.0f * G_PI / 180.0f,
1324 0.0f * G_PI / 180.0f);
1325
1326@@ -1067,28 +1193,30 @@
1327
1328 // bottom-right, left of the corner
1329 cairo_arc(cr,
1330- _align(x + width, odd) - _align(radius, odd),
1331- _align(y + height, odd) - _align(radius, odd),
1332- _align(radius, odd),
1333+ _align(x + width - radius, odd),
1334+ _align(y + height - radius, odd),
1335+ radius,
1336 0.0f * G_PI / 180.0f,
1337 90.0f * G_PI / 180.0f);
1338
1339 // bottom-left
1340 cairo_line_to(cr, _align(x, odd), _align(y + height, odd));
1341
1342- if (arrow == Arrow::LEFT && state == nux::VISUAL_STATE_PRESSED)
1343- {
1344+ if ((arrow == Arrow::LEFT || arrow == Arrow::BOTH) && state == nux::VISUAL_STATE_PRESSED)
1345+ {
1346 cairo_line_to(cr, _align(x, odd), _align(y + height / 2.0 + arrow_h, odd));
1347 cairo_line_to(cr, _align(x + arrow_w, odd), _align(y + height / 2.0, odd));
1348 cairo_line_to(cr, _align(x, odd), _align(y + height / 2.0 - arrow_h, odd));
1349- }
1350-
1351- // back to top-left
1352- cairo_close_path(cr);
1353+
1354+ // top-left,
1355+ cairo_line_to(cr, _align(x, odd), _align(y, odd));
1356+ }
1357+
1358 break;
1359 }
1360 }
1361
1362+
1363 void Style::Impl::ButtonOutlinePathSegment(cairo_t* cr, Segment segment)
1364 {
1365 double x = 0.0;
1366@@ -1563,7 +1691,7 @@
1367 1.0,
1368 (double) (garnish) + 1.0,
1369 (double) (garnish) + 1.0,
1370- 7.0,
1371+ BUTTON_CORNER_RADIUS,
1372 w - (double) (2 * garnish) - 2.0,
1373 h - (double) (2 * garnish) - 2.0);
1374 else
1375@@ -1571,7 +1699,7 @@
1376 1.0,
1377 (double) (garnish) + 0.5,
1378 (double) (garnish) + 0.5,
1379- 7.0,
1380+ BUTTON_CORNER_RADIUS,
1381 w - (double) (2 * garnish) - 1.0,
1382 h - (double) (2 * garnish) - 1.0);
1383
1384@@ -1664,7 +1792,7 @@
1385 cairo_move_to(cr, _align(x + width, odd), y);
1386 if (curve_bottom)
1387 {
1388- double radius = 7.0;
1389+ double radius = BUTTON_CORNER_RADIUS;
1390 LOG_DEBUG(logger) << "curve: " << _align(x + width, odd) << " - " << _align(y + height - radius, odd);
1391 // line to bottom-right corner
1392 cairo_line_to(cr, _align(x + width, odd), _align(y + height - radius, odd));
1393@@ -1771,7 +1899,7 @@
1394 1.0,
1395 (double) 0.5,
1396 (double) 0.5,
1397- 7.0,
1398+ BUTTON_CORNER_RADIUS,
1399 w - 1.0,
1400 h - 1.0);
1401
1402@@ -1785,6 +1913,7 @@
1403 bool Style::MultiRangeSegment(cairo_t* cr,
1404 nux::ButtonVisualState state,
1405 std::string const& label,
1406+ int font_px_size,
1407 Arrow arrow,
1408 Segment segment)
1409 {
1410@@ -1812,42 +1941,51 @@
1411 w -= 2.0;
1412 }
1413
1414- cairo_set_line_width(cr, pimpl->button_label_border_size_[state]);
1415-
1416- if (pimpl->button_label_border_size_[state] == 2.0)
1417- pimpl->RoundedRectSegment(cr,
1418- 1.0,
1419- x+1.0,
1420- y+1.0,
1421- (h-1.0) / 4.0,
1422- w-1.0,
1423- h-1.0,
1424- segment,
1425- arrow,
1426- state);
1427- else
1428- pimpl->RoundedRectSegment(cr,
1429+ cairo_set_line_width(cr, pimpl->button_label_border_size_[nux::VISUAL_STATE_NORMAL]);
1430+
1431+ pimpl->RoundedRectSegment(cr,
1432+ 1.0,
1433+ x,
1434+ y,
1435+ BUTTON_CORNER_RADIUS,
1436+ w,
1437+ h,
1438+ segment);
1439+
1440+ if (pimpl->button_label_fill_color_[state].alpha != 0.0)
1441+ {
1442+ cairo_set_source_rgba(cr, pimpl->button_label_fill_color_[state]);
1443+ cairo_fill_preserve(cr);
1444+ }
1445+
1446+ cairo_set_source_rgba(cr, pimpl->button_label_border_color_[nux::VISUAL_STATE_NORMAL]);
1447+ cairo_stroke(cr); // do not preseve path
1448+
1449+ if (state == nux::VISUAL_STATE_PRESSED)
1450+ {
1451+ int line_width = pimpl->button_label_border_size_[state];
1452+ cairo_set_line_width(cr, line_width);
1453+
1454+ pimpl->RoundedRectSegmentBorder(cr,
1455 1.0,
1456 x,
1457- y,
1458- h / 4.0,
1459+ y + line_width/2,
1460+ BUTTON_CORNER_RADIUS,
1461 w,
1462- h,
1463+ h - line_width,
1464 segment,
1465 arrow,
1466 state);
1467
1468- if (pimpl->button_label_fill_color_[state].alpha != 0.0)
1469- {
1470- cairo_set_source_rgba(cr, pimpl->button_label_fill_color_[state]);
1471- cairo_fill_preserve(cr);
1472+ cairo_set_source_rgba(cr, pimpl->button_label_border_color_[state]);
1473+ cairo_stroke(cr); // do not preseve path
1474 }
1475- cairo_set_source_rgba(cr, pimpl->button_label_border_color_[state]);
1476- cairo_stroke(cr);
1477+
1478+
1479 pimpl->Text(cr,
1480 pimpl->button_label_text_color_[state],
1481 label,
1482- 10); // 13px = 10pt
1483+ font_px_size);
1484
1485 return true;
1486 }
1487@@ -1888,9 +2026,7 @@
1488 h / 4.0,
1489 w,
1490 h,
1491- segment,
1492- arrow,
1493- nux::ButtonVisualState::VISUAL_STATE_PRESSED);
1494+ segment);
1495
1496 cairo_set_source_rgba(cr, nux::Color(1.0f, 1.0f, 1.0f, 0.5f));
1497 cairo_fill_preserve(cr);
1498
1499=== modified file 'unity-shared/DashStyle.h'
1500--- unity-shared/DashStyle.h 2012-12-17 09:28:31 +0000
1501+++ unity-shared/DashStyle.h 2013-01-30 10:36:20 +0000
1502@@ -111,6 +111,7 @@
1503 virtual bool MultiRangeSegment(cairo_t* cr,
1504 nux::ButtonVisualState state,
1505 std::string const& label,
1506+ int font_px_size,
1507 Arrow arrow,
1508 Segment segment);
1509