Merge lp:~elementary-apps/granite/sidebar-widget into lp:~elementary-pantheon/granite/granite

Proposed by Daniel Fore on 2016-08-28
Status: Work in progress
Proposed branch: lp:~elementary-apps/granite/sidebar-widget
Merge into: lp:~elementary-pantheon/granite/granite
Diff against target: 529 lines (+430/-34)
6 files modified
demo/GraniteDemo.vala (+90/-34)
lib/CMakeLists.txt (+4/-0)
lib/Widgets/Sidebar/Sidebar.vala (+30/-0)
lib/Widgets/Sidebar/SidebarExpandableRow.vala (+77/-0)
lib/Widgets/Sidebar/SidebarHeader.vala (+60/-0)
lib/Widgets/Sidebar/SidebarRow.vala (+169/-0)
To merge this branch: bzr merge lp:~elementary-apps/granite/sidebar-widget
Reviewer Review Type Date Requested Status
elementary Pantheon team 2016-08-28 Pending
Review via email: mp+304204@code.launchpad.net

Description of the change

== A brand new SourceList/Sidebar widget that isn't treeview based! ==

* Built around ListBox(Rows)
* Neato animations
* Badges
* Busy States,
* Row Action Buttons
* Uses the style class "sidebar" (There's a Gtk Style constant for this so it makes sense to just use it).

== Things that aren't baked into this branch yet: ==

* Make sure the row.activated signal is represented in the demo

* Drag and Drop
    * Mark a section as being draggable
    * Drag to re-arrange
    * Drag an item out to remove it (with poof)
    * Have a nice drop target for that section

* Nesting. Can't add another collapsable section into a section (useful for Mail)

* ProgressBars. To show usage of storage devices. Useful for Files

* Mark SourceList as deprecated

== Known Issues ==

* It's possible to make a selection in more than one section.

To post a comment you must log in.
Daniel Fore (danrabbit) wrote :

Default width is too large. Needs to be thinner to accommodate Files

988. By Daniel Fore on 2016-08-28

make min width 176 (16 * 11)

989. By Jeremy Wootten on 2016-09-05

Pass row-actived signal out of SidebarExpandableRow

JeanLuc (eviltwin1) wrote :

Hi there,
I just took a look at the code. Seems pretty reasonable so far, but personally I have a few things I'd like to be added to make it a fully functional replacement for the custom code I currently use in FeedReader (in addition to the things that you already listed above):

- the ability to have the little collapse/expand arrows on the left side. This is useful if the category itself also needs to have a badge.
- the ability to pack a custom widget in place of the badge/action-button or some sort of option to have a badge as default but show the action-button on hover: FeedReader shows the count of unread articles by default, but as soon as the mouse hovers the number it switches to a button that marks all articles of this specific feed or category as read.
- maybe the ability to have a custom widget instead of the icon_name for SidebarRow. For tags I use colored circles as "icons", which I draw right in the code to a Cairo.Surface. Would be nice if I could just use them as a icon directly.

That's all I can come up with from the top of my head that I personally am missing. Are these deviating too much from your vision of what a "sidebar" should look like?
I would be happy to contribute to this project if it will have everything I need to replace my own code once it's done.

Unmerged revisions

989. By Jeremy Wootten on 2016-09-05

Pass row-actived signal out of SidebarExpandableRow

988. By Daniel Fore on 2016-08-28

make min width 176 (16 * 11)

987. By Daniel Fore on 2016-08-28

add list-based sidebar widget

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'demo/GraniteDemo.vala'
2--- demo/GraniteDemo.vala 2016-08-06 23:40:54 +0000
3+++ demo/GraniteDemo.vala 2016-09-05 18:52:49 +0000
4@@ -76,7 +76,7 @@
5 create_headerbar ();
6 create_welcome ();
7 create_pickers ();
8- create_sourcelist ();
9+ create_sidebar ();
10 create_modebutton ();
11 create_dynamictab ();
12 create_alert ();
13@@ -121,7 +121,7 @@
14 private void create_welcome () {
15 var welcome = new Granite.Widgets.Welcome ("Sample Window", "This is a demo of the Granite library.");
16 welcome.append ("office-calendar", "TimePicker & DatePicker", "Widgets that allows users to easily pick a time or a date.");
17- welcome.append ("tag-new", "SourceList", "A widget that can display a list of items organized in categories.");
18+ welcome.append ("tag-new", "sidebar", "A widget that can display a list of items organized in categories.");
19 welcome.append ("object-inverse", "ModeButton", "This widget is a multiple option modal switch");
20 welcome.append ("document-open", "DynamicNotebook", "Tab bar widget designed for a variable number of tabs.");
21 welcome.append ("dialog-warning", "AlertView", "A View showing that an action is required to function.");
22@@ -134,7 +134,7 @@
23 break;
24 case 1:
25 home_button.show ();
26- main_stack.set_visible_child_name ("sourcelist");
27+ main_stack.set_visible_child_name ("sidebar");
28 break;
29 case 2:
30 home_button.show ();
31@@ -178,39 +178,95 @@
32 main_stack.add_named (grid, "pickers");
33 }
34
35- private void create_sourcelist () {
36- var label = new Gtk.Label ("No selected item");
37- var source_list = new Granite.Widgets.SourceList ();
38+ private void create_sidebar () {
39+ var personal = new Granite.Widgets.SidebarHeader ("Personal");
40+
41+ var home = new Granite.Widgets.SidebarRow ("Home", "user-home");
42+ var recent = new Granite.Widgets.SidebarRow ("Recent", "folder-recent");
43+ var documents = new Granite.Widgets.SidebarRow ("Documents", "folder-documents");
44+ var music = new Granite.Widgets.SidebarRow ("Music", "folder-music");
45+ var trash = new Granite.Widgets.SidebarRow ("Trash", "user-trash-empty");
46+ trash.icon_name = "user-trash-full";
47+
48+ var devices = new Granite.Widgets.SidebarHeader ("Devices");
49+
50+ var filesystem = new Granite.Widgets.SidebarRow ("Filesystem", "drive-harddisk");
51+ filesystem.action_icon_name = "media-eject-symbolic";
52+ filesystem.badge = 4;
53+ filesystem.tooltip_text = "/ - ext3/ext4 (217 GB Free of 243 GB)";
54+
55+ var usb_disk = new Granite.Widgets.SidebarRow ("USB Disk", "drive-removable-media");
56+ usb_disk.action_icon_name = "media-eject-symbolic";
57+ usb_disk.reveal_action = true;
58+
59+ var sidebar = new Granite.Widgets.Sidebar ();
60+ sidebar.add (personal);
61+ personal.add_child (home);
62+ personal.add_child (recent);
63+ personal.add_child (documents);
64+ personal.add_child (music);
65+ personal.add_child (trash);
66+ sidebar.add (devices);
67+ devices.add_child (filesystem);
68+ devices.add_child (usb_disk);
69+
70+ var filesystem_button = new Gtk.Button.with_label ("Toggle Eject Button");
71+
72+ var music_busy = new Gtk.Button.with_label ("Toggle Busy");
73+
74+ var add_folder = new Gtk.Button.with_label ("Add Folder");
75+
76+ var badge_spin = new Gtk.SpinButton.with_range (0, 99999, 7);
77+ badge_spin.bind_property ("value", home, "badge", BindingFlags.DEFAULT);
78+
79+ var usb_entry = new Gtk.Entry ();
80+ usb_entry.text = "USB Disk";
81+ usb_entry.bind_property ("text", usb_disk, "label", BindingFlags.DEFAULT);
82+
83+ var layout = new Gtk.Grid ();
84+ layout.row_spacing = 12;
85+ layout.orientation = Gtk.Orientation.VERTICAL;
86+ layout.width_request = 650;
87+ layout.margin = 24;
88+ layout.add (badge_spin);
89+ layout.add (music_busy);
90+ layout.add (add_folder);
91+ layout.add (filesystem_button);
92+ layout.add (usb_entry);
93
94 var paned = new Gtk.Paned (Gtk.Orientation.HORIZONTAL);
95- paned.pack1 (source_list, false, false);
96- paned.add2 (label);
97- paned.position = 150;
98-
99- var rand = new GLib.Rand ();
100- for (int letter = 'A'; letter <= 'Z'; letter++) {
101- var expandable_letter = new Granite.Widgets.SourceList.ExpandableItem ("Item %c".printf (letter));
102- source_list.root.add (expandable_letter);
103- for (int number = 1; number <= 10; number++) {
104- var number_item = new Granite.Widgets.SourceList.Item ("Subitem %d".printf (number));
105- var val = rand.next_int ();
106- if (val % 7 == 0)
107- number_item.badge = "1";
108- expandable_letter.add (number_item);
109- }
110- }
111-
112- main_stack.add_named (paned, "sourcelist");
113-
114- source_list.item_selected.connect ((item) => {
115- if (item == null) {
116- label.label = "No selected item";
117- return;
118- }
119-
120- if (item.badge != "" && item.badge != null)
121- item.badge = "";
122- label.label = "%s - %s".printf (item.parent.name, item.name);
123+ paned.add (sidebar);
124+ paned.add (layout);
125+
126+ main_stack.add_named (paned, "sidebar");
127+
128+ usb_disk.action_clicked.connect (() => {
129+ usb_disk.busy = true;
130+ Timeout.add (2000, () => {
131+ devices.remove_child (usb_disk);
132+ return false;
133+ });
134+ });
135+
136+ filesystem_button.clicked.connect (() => {
137+ if (filesystem.reveal_action) {
138+ filesystem.reveal_action = false;
139+ } else {
140+ filesystem.reveal_action = true;
141+ }
142+ });
143+
144+ music_busy.clicked.connect (() => {
145+ if (music.busy) {
146+ music.busy = false;
147+ } else {
148+ music.busy = true;
149+ }
150+ });
151+
152+ add_folder.clicked.connect (() => {
153+ var folder = new Granite.Widgets.SidebarRow ("Bookmark", "folder");
154+ personal.add_child (folder);
155 });
156 }
157
158
159=== modified file 'lib/CMakeLists.txt'
160--- lib/CMakeLists.txt 2015-10-09 17:31:04 +0000
161+++ lib/CMakeLists.txt 2016-09-05 18:52:49 +0000
162@@ -42,6 +42,10 @@
163 Widgets/DecoratedWindow.vala
164 Widgets/LightWindow.vala
165 Widgets/StatusBar.vala
166+ Widgets/Sidebar/Sidebar.vala
167+ Widgets/Sidebar/SidebarExpandableRow.vala
168+ Widgets/Sidebar/SidebarHeader.vala
169+ Widgets/Sidebar/SidebarRow.vala
170 Widgets/SidebarPaned.vala
171 Widgets/StorageBar.vala
172 Widgets/SourceList.vala
173
174=== added directory 'lib/Widgets/Sidebar'
175=== added file 'lib/Widgets/Sidebar/Sidebar.vala'
176--- lib/Widgets/Sidebar/Sidebar.vala 1970-01-01 00:00:00 +0000
177+++ lib/Widgets/Sidebar/Sidebar.vala 2016-09-05 18:52:49 +0000
178@@ -0,0 +1,30 @@
179+/*
180+* Copyright (c) 2016 elementary LLC (https://launchpad.net/granite)
181+*
182+* This program is free software; you can redistribute it and/or
183+* modify it under the terms of the GNU General Public
184+* License as published by the Free Software Foundation; either
185+* version 2 of the License, or (at your option) any later version.
186+*
187+* This program is distributed in the hope that it will be useful,
188+* but WITHOUT ANY WARRANTY; without even the implied warranty of
189+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
190+* General Public License for more details.
191+*
192+* You should have received a copy of the GNU General Public
193+* License along with this program; if not, write to the
194+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
195+* Boston, MA 02111-1307, USA.
196+*
197+*/
198+
199+namespace Granite.Widgets {
200+ public class Sidebar : Gtk.Grid {
201+ public Sidebar () {
202+ orientation = Gtk.Orientation.VERTICAL;
203+ get_style_context ().add_class (Gtk.STYLE_CLASS_SIDEBAR);
204+ width_request = 176;
205+ vexpand = true;
206+ }
207+ }
208+}
209
210=== added file 'lib/Widgets/Sidebar/SidebarExpandableRow.vala'
211--- lib/Widgets/Sidebar/SidebarExpandableRow.vala 1970-01-01 00:00:00 +0000
212+++ lib/Widgets/Sidebar/SidebarExpandableRow.vala 2016-09-05 18:52:49 +0000
213@@ -0,0 +1,77 @@
214+/*
215+* Copyright (c) 2016 elementary LLC (https://launchpad.net/granite)
216+*
217+* This program is free software; you can redistribute it and/or
218+* modify it under the terms of the GNU General Public
219+* License as published by the Free Software Foundation; either
220+* version 2 of the License, or (at your option) any later version.
221+*
222+* This program is distributed in the hope that it will be useful,
223+* but WITHOUT ANY WARRANTY; without even the implied warranty of
224+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
225+* General Public License for more details.
226+*
227+* You should have received a copy of the GNU General Public
228+* License along with this program; if not, write to the
229+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
230+* Boston, MA 02111-1307, USA.
231+*
232+*/
233+
234+namespace Granite.Widgets {
235+ public abstract class SidebarExpandableRow : Gtk.Grid {
236+ protected Gtk.ListBox children;
237+ protected Gtk.Revealer revealer;
238+ protected Gtk.Image disclosure_triangle;
239+
240+ public signal void row_activated (Gtk.ListBoxRow child);
241+
242+ public SidebarExpandableRow () {
243+ children = new Gtk.ListBox ();
244+
245+ revealer = new Gtk.Revealer ();
246+ revealer.reveal_child = true;
247+ revealer.transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN;
248+ revealer.add (children);
249+
250+ disclosure_triangle = new Gtk.Image.from_icon_name ("pan-down-symbolic", Gtk.IconSize.BUTTON);
251+
252+ revealer.notify["reveal-child"].connect (() => {
253+ if (revealer.reveal_child) {
254+ disclosure_triangle.icon_name = "pan-down-symbolic";
255+ } else {
256+ disclosure_triangle.icon_name = "pan-end-symbolic";
257+ }
258+ });
259+
260+ orientation = Gtk.Orientation.VERTICAL;
261+
262+ children.row_activated.connect ((row) => {
263+ row_activated (row);
264+ });
265+ }
266+
267+ public void toggle_reveal_children () {
268+ if (revealer.reveal_child) {
269+ revealer.reveal_child = false;
270+ } else {
271+ revealer.reveal_child = true;
272+ }
273+ }
274+
275+ public void add_child (SidebarRow child) {
276+ children.add (child);
277+ show_all ();
278+ child.reveal = true;
279+ }
280+
281+ public void remove_child (SidebarRow child) {
282+ child.reveal = false;
283+ Timeout.add (300, () => {
284+ children.remove (child);
285+ return false;
286+ });
287+ }
288+
289+ }
290+}
291
292=== added file 'lib/Widgets/Sidebar/SidebarHeader.vala'
293--- lib/Widgets/Sidebar/SidebarHeader.vala 1970-01-01 00:00:00 +0000
294+++ lib/Widgets/Sidebar/SidebarHeader.vala 2016-09-05 18:52:49 +0000
295@@ -0,0 +1,60 @@
296+/*
297+* Copyright (c) 2016 elementary LLC (https://launchpad.net/granite)
298+*
299+* This program is free software; you can redistribute it and/or
300+* modify it under the terms of the GNU General Public
301+* License as published by the Free Software Foundation; either
302+* version 2 of the License, or (at your option) any later version.
303+*
304+* This program is distributed in the hope that it will be useful,
305+* but WITHOUT ANY WARRANTY; without even the implied warranty of
306+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
307+* General Public License for more details.
308+*
309+* You should have received a copy of the GNU General Public
310+* License along with this program; if not, write to the
311+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
312+* Boston, MA 02111-1307, USA.
313+*
314+*/
315+
316+namespace Granite.Widgets {
317+ public class SidebarHeader : SidebarExpandableRow {
318+ public SidebarHeader (string label) {
319+ var header_label = new Gtk.Label (label);
320+ header_label.get_style_context ().add_class ("h4");
321+ header_label.halign = Gtk.Align.START;
322+ header_label.hexpand = true;
323+
324+ var header_revealer = new Gtk.Revealer ();
325+ header_revealer.transition_type = Gtk.RevealerTransitionType.CROSSFADE;
326+ header_revealer.add (disclosure_triangle);
327+
328+ var header_layout = new Gtk.Grid ();
329+ header_layout.add (header_label);
330+ header_layout.add (header_revealer);
331+
332+ var header = new Gtk.Button ();
333+ header.get_style_context ().add_class ("list-row");
334+ header.get_style_context ().remove_class ("button");
335+ header.add (header_layout);
336+
337+ add (header);
338+ add (revealer);
339+
340+ header.clicked.connect (() => {
341+ toggle_reveal_children ();
342+ });
343+
344+ header.enter_notify_event.connect (() => {
345+ header_revealer.reveal_child = true;
346+ return false;
347+ });
348+
349+ header.leave_notify_event.connect (() => {
350+ header_revealer.reveal_child = false;
351+ return false;
352+ });
353+ }
354+ }
355+}
356
357=== added file 'lib/Widgets/Sidebar/SidebarRow.vala'
358--- lib/Widgets/Sidebar/SidebarRow.vala 1970-01-01 00:00:00 +0000
359+++ lib/Widgets/Sidebar/SidebarRow.vala 2016-09-05 18:52:49 +0000
360@@ -0,0 +1,169 @@
361+/*
362+* Copyright (c) 2016 elementary LLC (https://launchpad.net/granite)
363+*
364+* This program is free software; you can redistribute it and/or
365+* modify it under the terms of the GNU General Public
366+* License as published by the Free Software Foundation; either
367+* version 2 of the License, or (at your option) any later version.
368+*
369+* This program is distributed in the hope that it will be useful,
370+* but WITHOUT ANY WARRANTY; without even the implied warranty of
371+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
372+* General Public License for more details.
373+*
374+* You should have received a copy of the GNU General Public
375+* License along with this program; if not, write to the
376+* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
377+* Boston, MA 02111-1307, USA.
378+*
379+*/
380+
381+namespace Granite.Widgets {
382+ public class SidebarRow : Gtk.ListBoxRow {
383+ public signal void action_clicked ();
384+
385+ private Gtk.Button button;
386+ private Gtk.Image button_image;
387+ private Gtk.Image icon;
388+ private Gtk.Label badge_label;
389+ private Gtk.Label row_label;
390+ private Gtk.Revealer badge_revealer;
391+ private Gtk.Revealer button_revealer;
392+ private Gtk.Revealer revealer;
393+ private Gtk.Spinner spinner;
394+ private Gtk.Stack button_stack;
395+
396+ public SidebarRow (string label, string icon_name) {
397+ icon = new Gtk.Image.from_icon_name (icon_name, Gtk.IconSize.BUTTON);
398+
399+ row_label = new Gtk.Label (label);
400+ row_label.ellipsize = Pango.EllipsizeMode.END;
401+ row_label.xalign = 0;
402+ row_label.hexpand = true;
403+
404+ badge_label = new Gtk.Label ("");
405+ badge_label.get_style_context ().add_class ("badge");
406+ badge_label.margin_end = 3;
407+ badge_label.valign = Gtk.Align.CENTER;
408+
409+ badge_revealer = new Gtk.Revealer ();
410+ badge_revealer.transition_type = Gtk.RevealerTransitionType.CROSSFADE;
411+ badge_revealer.no_show_all = true;
412+ badge_revealer.add (badge_label);
413+
414+ button_image = new Gtk.Image ();
415+ button_image.icon_size = Gtk.IconSize.BUTTON;
416+
417+ button = new Gtk.Button ();
418+ button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
419+ button.image = button_image;
420+
421+ spinner = new Gtk.Spinner ();
422+
423+ button_stack = new Gtk.Stack ();
424+ button_stack.transition_type = Gtk.StackTransitionType.CROSSFADE;
425+ button_stack.add (button);
426+ button_stack.add (spinner);
427+
428+ button_revealer = new Gtk.Revealer ();
429+ button_revealer.transition_type = Gtk.RevealerTransitionType.CROSSFADE;
430+ button_revealer.add (button_stack);
431+
432+ var layout = new Gtk.Grid ();
433+ layout.margin_start = 6;
434+ layout.add (icon);
435+ layout.add (row_label);
436+ layout.add (button_revealer);
437+ layout.add (badge_revealer);
438+
439+ revealer = new Gtk.Revealer ();
440+ revealer.transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN;
441+ revealer.add (layout);
442+
443+ add (revealer);
444+
445+ button.clicked.connect (() => {
446+ action_clicked ();
447+ });
448+ }
449+
450+ public string action_icon_name {
451+ set {
452+ button_image.icon_name = value;
453+ }
454+ }
455+
456+ public int badge {
457+ set {
458+ if (value != 0) {
459+ if (value > 999) {
460+ badge_label.label = "∞";
461+ } else {
462+ badge_label.label = value.to_string ();
463+ }
464+ badge_revealer.no_show_all = false;
465+ badge_revealer.show_all ();
466+ badge_revealer.reveal_child = true;
467+ } else {
468+ badge_revealer.reveal_child = false;
469+ Timeout.add (badge_revealer.transition_duration, () => {
470+ badge_revealer.visible = false;
471+ return false;
472+ });
473+ }
474+ }
475+ }
476+
477+ public bool busy {
478+ get {
479+ if (button_stack.visible_child == spinner) {
480+ return spinner.active;
481+ }
482+ return false;
483+ }
484+ set {
485+ spinner.active = value;
486+ if (value) {
487+ button_stack.visible_child = spinner;
488+ button_revealer.reveal_child = true;
489+ }
490+ }
491+ }
492+
493+ public string icon_name {
494+ set {
495+ icon.icon_name = value;
496+ }
497+ }
498+
499+ public string label {
500+ get {
501+ return row_label.label;
502+ }
503+ set {
504+ row_label.label = value;
505+ }
506+ }
507+
508+ public bool reveal {
509+ set {
510+ revealer.reveal_child = value;
511+ }
512+ }
513+
514+ public bool reveal_action {
515+ get {
516+ if (button_stack.visible_child == button) {
517+ return button_revealer.reveal_child;
518+ }
519+ return false;
520+ }
521+ set {
522+ if (value) {
523+ button_stack.visible_child = button;
524+ }
525+ button_revealer.reveal_child = value;
526+ }
527+ }
528+ }
529+}

Subscribers

People subscribed via source and target branches