Commit 8475c51e authored by Dylan McCall's avatar Dylan McCall

Styled popups for pauses (and eventually all breaks).

Initial separation of schedulers from user interface.
parent 66f20aa8
@define-color bg_inner rgba(15, 15, 15, 0.92);
@define-color bg_outer rgba(0, 0, 0, 0.96);
GtkWindow {
background-color: @bg_inner;
background-image:-gtk-gradient (linear,
center top,
center bottom,
color-stop (0, @bg_outer),
color-stop (0.1, @bg_inner),
color-stop (0.9, @bg_inner),
color-stop (1, @bg_outer));
border-radius: 8;
color: #ffffff;
/*font:Ubuntu Bold 14;*/
}
......@@ -36,6 +36,8 @@ targets:
- Scheduler.vala
- RestScheduler.vala
- PauseScheduler.vala
- BreakOverlay.vala
- PauseView.vala
uses:
- magic_core
packages:
......
public class BreakOverlay : Gtk.Window {
public BreakOverlay() {
Object(type: Gtk.WindowType.POPUP);
this.realize.connect(this.on_realize);
Gdk.Screen screen = this.get_screen();
if (screen.is_composited()) {
Gdk.Visual rgba_visual = screen.get_rgba_visual();
if (rgba_visual != null) this.set_visual(rgba_visual);
}
/* we don't want any input, ever */
/* FIXME: surely we can just say what input we want instead of making a region? */
this.input_shape_combine_region(new Cairo.Region());
}
private void on_realize() {
Gdk.Screen screen = this.get_screen();
int monitor = screen.get_monitor_at_window(this.get_window());
Gdk.Rectangle geom;
screen.get_monitor_geometry(monitor, out geom);
this.set_default_size((int)(geom.width * 0.7), (int)(geom.height * 0.75));
this.set_position(Gtk.WindowPosition.CENTER);
}
public set_time_remaining(int seconds) {
}
/** Set a reassuring message to accompany the break timer */
public set_message(string message) {
}
}
namespace Magic {
[Import] public static extern void begin ();
[Import] public static extern uint32 get_idle_time ();
[Import] public static extern void begin();
[Import] public static extern uint32 get_idle_time();
}
using Notify;
public class PauseScheduler : Scheduler {
public signal void active_update(int time_remaining);
/* TODO: test if we should manually add idle time every second,
* (which implicitly pauses when computer is in use),
*(which implicitly pauses when computer is in use),
* or use a real Timer
*/
private Timer time_idle;
private Timer break_timer;
public PauseScheduler () {
public PauseScheduler() {
/* 480s = 8 minutes */
/* 20s */
base(480, 20);
/* 20s duration */
base(5, 3);
time_idle = new Timer();
this.break_timer = new Timer();
}
/**
* Per-second timeout during pause break.
*/
private bool active_timeout () {
/* TODO: Delay during active computer use */
/* Update user interface (count every minute) */
stdout.printf("%f spent idle", time_idle.elapsed());
private bool active_timeout() {
/* Delay during active computer use */
int idle_time = (int)(Magic.get_idle_time() / 1000);
if (idle_time < this.break_timer.elapsed()) {
this.break_timer.start();
}
/* Update watchers (count every minute) */
int time_elapsed_seconds = (int)Math.round(this.break_timer.elapsed());
int time_remaining = (int)this.duration - time_elapsed_seconds;
/* End break */
if (time_idle.elapsed() >= duration) {
end(false);
if (time_remaining < 0) {
this.end();
return false;
} else {
this.active_update(time_remaining);
return true;
}
}
public override void activate () {
base.activate ();
/* TODO: Start with a notification, then transition to a more visible interface after 30s */
time_idle.start();
Timeout.add_seconds (1, active_timeout);
public override void activate() {
base.activate();
Notify.Notification notification = new Notification ("Micro break", "It's time for a short break.", null);
notification.show();
break_timer.start();
Timeout.add_seconds(1, active_timeout);
}
public override void end (bool quiet = false) {
base.end(quiet);
/* display a happy notification if quiet == false */
public override void end() {
base.end();
}
}
using Notify;
class TimerString : Object {
private const string second_remaining_template = "%d second remaining";
private const string seconds_remaining_template = "%d seconds remaining";
private const string minute_remaining_template = "%d minute remaining";
private const string minutes_remaining_template = "%d minutes remaining";
public static string get_countdown_for_seconds (int seconds) {
if (seconds > 60) {
/* count remaining time in minutes */
int minutes = seconds / 60;
return minutes_remaining_template.printf(minutes);
} else {
/* count remaining time in seconds */
/* FIXME: should be intervals of ten, then five, then one */
return seconds_remaining_template.printf(seconds);
/* FIXME: handle plurals, nicely, through gettext or gtk or whoever */
}
}
}
class PauseView : Object {
private BreakOverlay break_overlay;
private Gtk.Label timer_label;
public PauseView(PauseScheduler scheduler) {
scheduler.started.connect(this.break_started_cb);
scheduler.finished.connect(this.break_finished_cb);
scheduler.active_update.connect(this.break_active_update_cb);
}
private void show_break_overlay() {
/* FIXME: ask the application for a break dialog. That way RestView can take it over as necessary */
break_overlay = new BreakOverlay();
break_overlay.set_title("Micro break");
Gtk.Alignment alignment = new Gtk.Alignment(0.5f, 0.5f, 1.0f, 1.0f);
alignment.set_padding(8, 8, 12, 12);
Gtk.VBox container = new Gtk.VBox(false, 12);
Gtk.Label break_message = new Gtk.Label("Just give your eyes and your fingers a moment of rest");
this.timer_label = new Gtk.Label("");
container.pack_end(break_message, false);
container.pack_end(this.timer_label, true);
alignment.add(container);
break_overlay.add(alignment);
break_overlay.show_all();
}
private void update_break_overlay(int time_remaining) {
stdout.printf("Pause break. %f remaining\n", time_remaining);
if (this.timer_label != null) {
string new_label_text = TimerString.get_countdown_for_seconds(time_remaining);
this.timer_label.set_text(new_label_text);
}
}
private void hide_break_overlay() {
if (this.break_overlay != null) {
this.break_overlay.destroy();
this.break_overlay = null;
}
}
private void break_started_cb() {
/** Initial notification period before more aggressive UI */
Notify.Notification notification = new Notification("Micro break", "It's time for a short break.", null);
notification.set_urgency(Notify.Urgency.CRITICAL);
notification.show();
Timeout.add_seconds(5, () => {
notification.close();
stdout.printf("Notification closed?");
/* show the big break message, hook up to active_timeout */
this.show_break_overlay();
return false;
});
}
private void break_active_update_cb(int time_remaining) {
this.update_break_overlay(time_remaining);
}
private void break_finished_cb() {
/* TODO: show notification only if break dialog was not visible */
/* FIXME: tell gnome shell this notification is transient! */
Notify.Notification notification = new Notification("Micro break finished", "", null);
notification.set_urgency(Notify.Urgency.LOW);
notification.show();
this.hide_break_overlay();
}
}
public class RestScheduler : Scheduler {
/* TODO: test if we should manually add idle time every second,
* (which implicitly pauses when computer is in use),
*(which implicitly pauses when computer is in use),
* or use a real Timer
*/
private Timer time_idle;
public RestScheduler () {
public RestScheduler() {
/* 2400s = 40 minutes */
/* 360s = 6 minutes */
base(2400, 360);
......@@ -16,31 +16,29 @@ public class RestScheduler : Scheduler {
/**
* Per-second timeout during rest break.
*/
private bool active_timeout () {
private bool active_timeout() {
/* TODO: Delay during active computer use */
/* Update user interface (count every minute) */
stdout.printf("%f spent idle", time_idle.elapsed());
/* Update user interface(count every minute) */
stdout.printf("Rest break. %f spent idle\n", time_idle.elapsed());
/* End break */
if (time_idle.elapsed() >= duration) {
end(false);
if (Math.round(this.time_idle.elapsed()) >= this.duration) {
this.end();
return false;
} else {
return true;
}
}
public override void activate () {
public override void activate() {
base.activate();
/* TODO: Start with a notification, then transition to a more visible interface after 60s */
time_idle.start();
Timeout.add_seconds (1, active_timeout);
Timeout.add_seconds(1, active_timeout);
}
public override void end (bool quiet = false) {
base.end(quiet);
/* display a happy notification if quiet == false */
public override void end() {
base.end();
}
}
/* TODO: notification when user is away for rest duration */
/* TODO: replace pause break if appropriate */
......@@ -2,43 +2,49 @@
* Interface for a type of break. Each break type has a unique feedback
* mechanism triggered by calling the begin method.
*/
public abstract class Scheduler {
public uint interval {get; set;}
public abstract class Scheduler : Object {
public int interval {get; set;}
/* TODO: duration should be private to child class */
public uint duration {get; set;}
public int duration {get; set;}
/** Called when a break starts to run */
public signal void started ();
public signal void started();
/** Called when a break is finished running */
public signal void finished ();
public signal void finished();
public enum SchedulerState {
WAITING,
ACTIVE
}
private static SchedulerState state;
protected Timer start_timer;
public Scheduler (uint interval, uint duration) {
public Scheduler(int interval, int duration) {
this.interval = interval;
this.duration = duration;
start_timer = new Timer();
/* FIXME: We need LCD of duration and interval so we catch idle>duration as well as start the rest on time */
Timeout.add_seconds (duration, idle_timeout);
Timeout.add_seconds(duration, idle_timeout);
}
/**
* Periodically tests if it is time for a break
*/
protected bool idle_timeout () {
uint idle_time = (uint)Magic.get_idle_time () / 1000;
protected bool idle_timeout() {
int idle_time = (int)(Magic.get_idle_time() / 1000);
/* Reset timer if the user takes a sufficiently long break */
if ((idle_time) > duration) {
stdout.printf("Resetting break timer!\n");
if (idle_time > duration) {
stdout.printf("Resetting break timer for %s!\n", this.get_type().name());
start_timer.start();
}
/* Start break if the user has been active for interval */
if (start_timer.elapsed() >= interval) {
stdout.printf("Activating break!\n");
if (start_timer.elapsed() >= interval && this.state == SchedulerState.WAITING) {
stdout.printf("Activating break %s!\n", this.get_type().name());
activate();
}
......@@ -48,12 +54,14 @@ public abstract class Scheduler {
/**
* It is time for a break!
*/
public virtual void activate () {
started();
public virtual void activate() {
this.state = SchedulerState.ACTIVE;
this.started();
}
public virtual void end (bool quiet = false) {
finished();
public virtual void end() {
this.state = SchedulerState.WAITING;
this.finished();
}
}
/*
Brain Break
...Or Yet Another RSI Prevention Tool. This time prettier and happier.
Copyright (c) 2011, Dylan McCall and Brain Break contributors.
Copyright(c) 2011, Dylan McCall and Brain Break contributors.
<dylanmccall@gmail.com>
-----
......@@ -15,7 +15,7 @@ things.
Brain Break should stay out of the way and be as non-destructive as
possible, accommodating users regardless of their current tasks without
requiring that they change its aggressiveness themselves, (for example
requiring that they change its aggressiveness themselves,(for example
between Quiet, Postponed and Active mode in Workrave). It should avoid
threatening users with things such as permanent statistics, but provide
them with incentives to be healthy that fit into the moment at hand.
......@@ -44,26 +44,44 @@ public class BrainBreak : Gtk.Application {
private static RestScheduler rest;
private static PauseScheduler pause;
public BrainBreak () {
Object (application_id: app_id, flags: ApplicationFlags.FLAGS_NONE);
GLib.Environment.set_application_name (app_name);
private static PauseView pause_view;
public BrainBreak() {
Object(application_id: app_id, flags: ApplicationFlags.FLAGS_NONE);
GLib.Environment.set_application_name(app_name);
}
public override void activate () {
public override void activate() {
}
public override void startup () {
public override void startup() {
this.hold(); /* we're doing stuff, even if no windows are open */
Notify.init (app_id);
Magic.begin();
rest = new RestScheduler();
pause = new PauseScheduler();
/* set up custom gtk style for application */
Gdk.Screen screen = Gdk.Screen.get_default();
Gtk.CssProvider style_provider = new Gtk.CssProvider();
/* FIXME: of course, we should load data files in a smarter way */
style_provider.load_from_path("data/style.css");
Gtk.StyleContext.add_provider_for_screen(screen,
style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
Notify.init(app_name);
//Gtk.Settings settings = Gtk.Settings.get_default();
//settings.gtk_application_prefer_dark_theme = true;
this.rest = new RestScheduler();
this.pause = new PauseScheduler();
this.pause_view = new PauseView(pause);
}
}
public int main (string[] args) {
public int main(string[] args) {
Gtk.init(ref args);
BrainBreak application = new BrainBreak();
return application.run(args);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment