From 779aef2fe2856e26f8790eba9a6984a814267ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Fri, 2 May 2025 17:19:32 -0700 Subject: [PATCH] Show progress in sidebar --- data/Application.css | 14 +- meson.build | 1 + src/Widgets/CircularProgressBar.vala | 218 +++++++++++++++++++++++++++ src/Widgets/SourceRow.vala | 15 +- 4 files changed, 232 insertions(+), 16 deletions(-) create mode 100644 src/Widgets/CircularProgressBar.vala diff --git a/data/Application.css b/data/Application.css index 465e4018c..cf18759cc 100644 --- a/data/Application.css +++ b/data/Application.css @@ -75,16 +75,12 @@ row.task.drag-active { min-width: 86px; /* https://github.com/elementary/granite/issues/577#issuecomment-1318979272 */ } -.source-color { - background: @colorAccent; - border: 1px solid @borders; +.circular-progress-bar { + border: 2px solid @accent_color; border-radius: 50%; - box-shadow: - inset 0 1px 0 0 alpha(@inset_dark_color, 0.7), - inset 0 0 0 1px alpha(@inset_dark_color, 0.3), - 0 1px 0 0 alpha(@bg_highlight_color, 0.3); - min-height: 14px; - min-width: 14px; + min-width: 8px; + min-height: 8px; + padding: 2px; } .sidebar image { diff --git a/meson.build b/meson.build index 07f1293bd..cd679bbf6 100644 --- a/meson.build +++ b/meson.build @@ -68,6 +68,7 @@ executable( 'src/Util.vala', 'src/Views/ListView.vala', 'src/Views/ScheduledView.vala', + 'src/Widgets/CircularProgressBar.vala', 'src/Widgets/EditableLabel.vala', 'src/Widgets/EntryPopover/DateTime.vala', 'src/Widgets/EntryPopover/Generic.vala', diff --git a/src/Widgets/CircularProgressBar.vala b/src/Widgets/CircularProgressBar.vala new file mode 100644 index 000000000..9405648d3 --- /dev/null +++ b/src/Widgets/CircularProgressBar.vala @@ -0,0 +1,218 @@ +/* -*- Mode: Vala; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ +/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab : */ +/* + * Custom Gtk.Widget to provide a circular progress bar. + * It extends/subclasses Gtk.Bin instead of Gtk.DrawingArea. + * + * Based on José Miguel Fonte's Vala Circular Progress Bar + * https://github.com/phastmike/vala-circular-progress-bar + */ + +using Gtk; +using Cairo; + +public class Tasks.CircularProgressBar : Granite.Bin { + public double percentage { + get { + return circularProgressBar.percentage; + } + + set { + circularProgressBar.percentage = value; + } + } + + public string color { + set { + circularProgressBar.progress_fill_color = value; + } + } + + private _CircularProgressBar circularProgressBar; // vala-lint=naming-convention + + public CircularProgressBar () { + } + + construct { + add_css_class ("circular-progress-bar"); + + circularProgressBar = new _CircularProgressBar (); + + child = circularProgressBar; + } +} + +public class _CircularProgressBar : Gtk.DrawingArea { // vala-lint=naming-convention + private int _line_width; + private double _percentage; + private string _center_fill_color; + private string _radius_fill_color; + private string _progress_fill_color; + + [Description (nick = "Center Fill", blurb = "Center Fill toggle")] + public bool center_filled {set; get; default = false;} + + [Description (nick = "Radius Fill", blurb = "Radius Fill toggle")] + public bool radius_filled {set; get; default = false;} + + [Description (nick = "Line Cap", blurb = "Line Cap for stroke as in Cairo.LineCap")] + public Cairo.LineCap line_cap {set; get; default = Cairo.LineCap.BUTT;} + + [Description (nick = "Inside circle fill color", blurb = "Center pad fill color (Check Gdk.RGBA parse method)")] + public string center_fill_color { + get { + return _center_fill_color; + } + set { + var color = Gdk.RGBA (); + if (color.parse (value)) { + _center_fill_color = value; + } + } + } + + [Description (nick = "Circular radius fill color", blurb = "The circular pad fill color (Check GdkRGBA parse method)")] + public string radius_fill_color { + get { + return _radius_fill_color; + } + set { + var color = Gdk.RGBA (); + if (color.parse (value)) { + _radius_fill_color = value; + } + } + } + + [Description (nick = "Progress fill color", blurb = "Progress line color (Check GdkRGBA parse method)")] + public string progress_fill_color { + get { + return _progress_fill_color; + } + set { + var color = Gdk.RGBA (); + if (color.parse (value)) { + _progress_fill_color = value; + } + } + } + + [Description (nick = "Circle width", blurb = "The circle radius line width")] + public int line_width { + get { + return _line_width; + } + set { + if (value < 0) { + _line_width = 0; + } else if (value > calculate_radius ()) { + _line_width = calculate_radius (); + } else { + _line_width = value; + } + } + } + + [Description (nick = "Percentage/Value", blurb = "The percentage value [0.0 ... 1.0]")] + public double percentage { + get { + return _percentage; + } + set { + if (value > 1.0) { + _percentage = 1.0; + } else if (value < 0.0) { + _percentage = 0.0; + } else { + _percentage = value; + } + } + } + + construct { + _line_width = 0; + _percentage = 0.5; + _center_fill_color = "#adadad"; // vala-lint=double-spaces + _radius_fill_color = "#d3d3d3"; // vala-lint=double-spaces + _progress_fill_color = "#4a90d9"; + } + + public _CircularProgressBar () { + set_draw_func (draw); + + notify.connect (() => { + queue_draw (); + }); + } + + private int calculate_radius () { + return int.min (get_width () / 2, get_height () / 2) - 1; + } + + public override Gtk.SizeRequestMode get_request_mode () { + return Gtk.SizeRequestMode.CONSTANT_SIZE; + } + + public void draw (DrawingArea da, Cairo.Context cr, int width, int height) { + int delta; + Gdk.RGBA color; + + cr.save (); + + color = Gdk.RGBA (); + + var center_x = get_width () / 2; + var center_y = get_height () / 2; + var radius = calculate_radius (); + + if (radius - line_width < 0) { + delta = 0; + line_width = radius; + } else { + delta = radius - (line_width / 2); + } + + color = Gdk.RGBA (); + cr.set_line_cap (line_cap); + cr.set_line_width (line_width); + + // Center Fill + if (center_filled == true) { + cr.arc (center_x, center_y, delta, 0, 2 * Math.PI); + color.parse (center_fill_color); + Gdk.cairo_set_source_rgba (cr, color); + cr.fill (); + } + + // Radius Fill + if (radius_filled == true) { + cr.arc (center_x, center_y, delta, 0, 2 * Math.PI); + color.parse (radius_fill_color); + Gdk.cairo_set_source_rgba (cr, color); + cr.stroke (); + } + + // Progress/Percentage Fill + if (percentage > 0) { + color.parse (progress_fill_color); + Gdk.cairo_set_source_rgba (cr, color); + + if (line_width == 0) { + cr.move_to (center_x, center_y); + cr.arc (center_x, + center_y, + delta + 1, + 1.5 * Math.PI, + (1.5 + percentage * 2 ) * Math.PI); + cr.fill (); + } else { + cr.arc (center_x, + center_y, + delta, + 1.5 * Math.PI, + (1.5 + percentage * 2 ) * Math.PI); + cr.stroke (); + } + } + } +} diff --git a/src/Widgets/SourceRow.vala b/src/Widgets/SourceRow.vala index 59ceeda7c..6118f953d 100644 --- a/src/Widgets/SourceRow.vala +++ b/src/Widgets/SourceRow.vala @@ -6,7 +6,7 @@ public class Tasks.Widgets.SourceRow : Gtk.ListBoxRow { public E.Source source { get; construct; } - private Gtk.Grid source_color; + private CircularProgressBar progress_circle; private Gtk.Image status_image; private Gtk.Label display_name_label; private Gtk.Stack status_stack; @@ -17,10 +17,7 @@ public class Tasks.Widgets.SourceRow : Gtk.ListBoxRow { } construct { - source_color = new Gtk.Grid () { - valign = Gtk.Align.CENTER - }; - source_color.add_css_class ("source-color"); + progress_circle = new CircularProgressBar (); display_name_label = new Gtk.Label (source.display_name) { halign = Gtk.Align.START, @@ -44,7 +41,7 @@ public class Tasks.Widgets.SourceRow : Gtk.ListBoxRow { var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { margin_end = 6 }; - box.append (source_color); + box.append (progress_circle); box.append (display_name_label); box.append (status_stack); @@ -162,7 +159,11 @@ public class Tasks.Widgets.SourceRow : Gtk.ListBoxRow { } public void update_request () { - Tasks.Application.set_task_color (source, source_color); + Tasks.Application.set_task_color (source, progress_circle); + + unowned var task_list = (E.SourceTaskList?) source.get_extension (E.SOURCE_EXTENSION_TASK_LIST); + // Ensure we get a valid CSS color, not including FF + progress_circle.color = task_list.dup_color ().slice (0, 7); display_name_label.label = source.display_name;