summaryrefslogtreecommitdiff
path: root/src/renderers
diff options
context:
space:
mode:
Diffstat (limited to 'src/renderers')
-rw-r--r--src/renderers/centerRenderer.vala146
-rw-r--r--src/renderers/pieRenderer.vala208
-rw-r--r--src/renderers/pieWindow.vala263
-rw-r--r--src/renderers/sliceRenderer.vala182
4 files changed, 799 insertions, 0 deletions
diff --git a/src/renderers/centerRenderer.vala b/src/renderers/centerRenderer.vala
new file mode 100644
index 0000000..c30e9ce
--- /dev/null
+++ b/src/renderers/centerRenderer.vala
@@ -0,0 +1,146 @@
+/*
+Copyright (c) 2011 by Simon Schneegans
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+// Renders the center of a Pie.
+
+public class CenterRenderer : GLib.Object {
+
+ private unowned PieRenderer parent;
+ private unowned Image? caption;
+ private Color color;
+
+ private AnimatedValue activity;
+ private AnimatedValue alpha;
+
+ public CenterRenderer(PieRenderer parent) {
+ this.parent = parent;
+ this.activity = new AnimatedValue.linear(0.0, 0.0, Config.global.theme.transition_time);
+ this.alpha = new AnimatedValue.linear(0.0, 1.0, Config.global.theme.fade_in_time);
+ this.color = new Color();
+ this.caption = null;
+ }
+
+ public void fade_out() {
+ this.activity.reset_target(0.0, Config.global.theme.fade_out_time);
+ this.alpha.reset_target(0.0, Config.global.theme.fade_out_time);
+ }
+
+ public void set_active_slice(SliceRenderer? active_slice) {
+ if (active_slice == null) {
+ this.activity.reset_target(0.0, Config.global.theme.transition_time);
+ } else {
+ this.activity.reset_target(1.0, Config.global.theme.transition_time);
+ this.caption = active_slice.caption;
+ this.color = active_slice.color;
+ }
+ }
+
+ public void draw(double frame_time, Cairo.Context ctx, double angle, double distance) {
+
+ var layers = Config.global.theme.center_layers;
+
+ this.activity.update(frame_time);
+ this.alpha.update(frame_time);
+
+ foreach (var layer in layers) {
+
+ ctx.save();
+
+ double active_speed = (layer.active_rotation_mode == CenterLayer.RotationMode.TO_MOUSE) ?
+ 0.0 : layer.active_rotation_speed;
+ double inactive_speed = (layer.inactive_rotation_mode == CenterLayer.RotationMode.TO_MOUSE) ?
+ 0.0 : layer.inactive_rotation_speed;
+ double max_scale = layer.active_scale*this.activity.val
+ + layer.inactive_scale*(1.0-this.activity.val);
+ double max_alpha = layer.active_alpha*this.activity.val
+ + layer.inactive_alpha*(1.0-this.activity.val);
+ double colorize = ((layer.active_colorize == true) ? this.activity.val : 0.0)
+ + ((layer.inactive_colorize == true) ? 1.0 - this.activity.val : 0.0);
+ double max_rotation_speed = active_speed*this.activity.val
+ + inactive_speed*(1.0-this.activity.val);
+ CenterLayer.RotationMode rotation_mode = ((this.activity.val > 0.5) ?
+ layer.active_rotation_mode : layer.inactive_rotation_mode);
+
+ if (rotation_mode == CenterLayer.RotationMode.TO_MOUSE) {
+ double diff = angle-layer.rotation;
+ max_rotation_speed = layer.active_rotation_speed*this.activity.val
+ + layer.inactive_rotation_speed*(1.0-this.activity.val);
+ double smoothy = fabs(diff) < 0.9 ? fabs(diff) + 0.1 : 1.0;
+ double step = max_rotation_speed*frame_time*smoothy;
+
+ if (fabs(diff) <= step || fabs(diff) >= 2.0*PI - step)
+ layer.rotation = angle;
+ else {
+ if ((diff > 0 && diff < PI) || diff < -PI) layer.rotation += step;
+ else layer.rotation -= step;
+ }
+
+ } else if (rotation_mode == CenterLayer.RotationMode.TO_ACTIVE) {
+ max_rotation_speed *= this.activity.val;
+
+ double slice_angle = parent.slice_count() > 0 ? 2*PI/parent.slice_count() : 0;
+ double direction = (int)((angle+0.5*slice_angle) / (slice_angle))*slice_angle;
+ double diff = direction-layer.rotation;
+ double step = max_rotation_speed*frame_time;
+
+ if (fabs(diff) <= step || fabs(diff) >= 2.0*PI - step)
+ layer.rotation = direction;
+ else {
+ if ((diff > 0 && diff < PI) || diff < -PI) layer.rotation += step;
+ else layer.rotation -= step;
+ }
+
+ } else layer.rotation += max_rotation_speed*frame_time;
+
+ layer.rotation = fmod(layer.rotation+2*PI, 2*PI);
+
+ if (colorize > 0.0) ctx.push_group();
+
+ ctx.rotate(layer.rotation);
+ ctx.scale(max_scale, max_scale);
+ layer.image.paint_on(ctx, this.alpha.val*max_alpha);
+
+ if (colorize > 0.0) {
+ ctx.set_operator(Cairo.Operator.ATOP);
+ ctx.set_source_rgb(this.color.r, this.color.g, this.color.b);
+ ctx.paint_with_alpha(colorize);
+
+ ctx.set_operator(Cairo.Operator.OVER);
+ ctx.pop_group_to_source();
+ ctx.paint();
+ }
+
+ ctx.restore();
+ }
+
+ // draw caption
+ if (Config.global.theme.caption && caption != null && this.activity.val > 0) {
+ ctx.save();
+ ctx.identity_matrix();
+ int pos = this.parent.get_size()/2;
+ ctx.translate(pos, (int)(Config.global.theme.caption_position) + pos);
+ caption.paint_on(ctx, this.activity.val*this.alpha.val);
+ ctx.restore();
+ }
+ }
+}
+
+}
diff --git a/src/renderers/pieRenderer.vala b/src/renderers/pieRenderer.vala
new file mode 100644
index 0000000..5b706f4
--- /dev/null
+++ b/src/renderers/pieRenderer.vala
@@ -0,0 +1,208 @@
+/*
+Copyright (c) 2011 by Simon Schneegans
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+/////////////////////////////////////////////////////////////////////////
+/// This class renders a Pie. In order to accomplish that, it owns a
+/// CenterRenderer and some SliceRenderers.
+/////////////////////////////////////////////////////////////////////////
+
+public class PieRenderer : GLib.Object {
+
+ public int quick_action { get; private set; }
+ public int active_slice { get; private set; }
+ public bool show_hotkeys { get; set; }
+
+ private int size;
+ private Gee.ArrayList<SliceRenderer?> slices;
+ private CenterRenderer center;
+ private bool key_board_control = false;
+
+ public PieRenderer() {
+ this.slices = new Gee.ArrayList<SliceRenderer?>();
+ this.center = new CenterRenderer(this);
+ this.quick_action = -1;
+ this.active_slice = -2;
+ this.size = 0;
+ }
+
+ public void load_pie(Pie pie) {
+ this.slices.clear();
+
+ int count = 0;
+ foreach (var group in pie.action_groups) {
+ foreach (var action in group.actions) {
+ var renderer = new SliceRenderer(this);
+ this.slices.add(renderer);
+ renderer.load(action, slices.size-1);
+
+ if (action.is_quick_action) {
+ this.quick_action = count;
+ }
+
+ ++count;
+ }
+ }
+
+ this.set_highlighted_slice(this.quick_action);
+
+ this.size = (int)fmax(2*Config.global.theme.radius + 2*Config.global.theme.slice_radius*Config.global.theme.max_zoom,
+ 2*Config.global.theme.center_radius);
+
+ // increase size if there are many slices
+ if (slices.size > 0) {
+ this.size = (int)fmax(this.size,
+ (((Config.global.theme.slice_radius + Config.global.theme.slice_gap)/tan(PI/slices.size))
+ + Config.global.theme.slice_radius)*2*Config.global.theme.max_zoom);
+ }
+ }
+
+ public void activate() {
+ if (this.active_slice >= 0 && this.active_slice < this.slices.size)
+ slices[active_slice].activate();
+ this.cancel();
+ }
+
+ public void cancel() {
+ foreach (var slice in this.slices)
+ slice.fade_out();
+
+ center.fade_out();
+ }
+
+ public void select_up() {
+ int bottom = this.slice_count()/4;
+ int top = this.slice_count()*3/4;
+
+ if (this.active_slice == -1 || this.active_slice == bottom)
+ this.set_highlighted_slice(top);
+ else if (this.active_slice > bottom && this.active_slice < top)
+ this.set_highlighted_slice(this.active_slice+1);
+ else if (this.active_slice != top)
+ this.set_highlighted_slice((this.active_slice-1+this.slice_count())%this.slice_count());
+ }
+
+ public void select_down() {
+ int bottom = this.slice_count()/4;
+ int top = this.slice_count()*3/4;
+
+ if (this.active_slice == -1 || this.active_slice == top)
+ this.set_highlighted_slice(bottom);
+ else if (this.active_slice > bottom && this.active_slice < top)
+ this.set_highlighted_slice(this.active_slice-1);
+ else if (this.active_slice != bottom)
+ this.set_highlighted_slice((this.active_slice+1)%this.slice_count());
+ }
+
+ public void select_left() {
+ int left = this.slice_count()/2;
+ int right = 0;
+
+ if (this.active_slice == -1 || this.active_slice == right)
+ this.set_highlighted_slice(left);
+ else if (this.active_slice > left)
+ this.set_highlighted_slice(this.active_slice-1);
+ else if (this.active_slice < left)
+ this.set_highlighted_slice(this.active_slice+1);
+ }
+
+ public void select_right() {
+ int left = this.slice_count()/2;
+ int right = 0;
+
+ if (this.active_slice == -1 || this.active_slice == left)
+ this.set_highlighted_slice(right);
+ else if (this.active_slice > left)
+ this.set_highlighted_slice((this.active_slice+1)%this.slice_count());
+ else if (this.active_slice < left && this.active_slice != right)
+ this.set_highlighted_slice((this.active_slice-1+this.slice_count())%this.slice_count());
+ }
+
+ public int slice_count() {
+ return slices.size;
+ }
+
+ public int get_size() {
+ return size;
+ }
+
+ public void draw(double frame_time, Cairo.Context ctx, int mouse_x, int mouse_y) {
+ double distance = sqrt(mouse_x*mouse_x + mouse_y*mouse_y);
+ double angle = 0.0;
+
+ if (this.key_board_control) {
+ angle = 2.0*PI*this.active_slice/(double)slice_count();
+ } else {
+
+ if (distance > 0) {
+ angle = acos(mouse_x/distance);
+ if (mouse_y < 0)
+ angle = 2*PI - angle;
+ }
+
+ int next_active_slice = this.active_slice;
+
+ if (distance < Config.global.theme.active_radius
+ && this.quick_action >= 0 && this.quick_action < this.slices.size) {
+
+ next_active_slice = this.quick_action;
+ angle = 2.0*PI*quick_action/(double)slice_count();
+ } else if (distance > Config.global.theme.active_radius && this.slice_count() > 0) {
+ next_active_slice = (int)(angle*slices.size/(2*PI) + 0.5) % this.slice_count();
+ } else {
+ next_active_slice = -1;
+ }
+
+ this.set_highlighted_slice(next_active_slice);
+ }
+
+ center.draw(frame_time, ctx, angle, distance);
+
+ foreach (var slice in this.slices)
+ slice.draw(frame_time, ctx, angle, distance);
+ }
+
+ public void on_mouse_move() {
+ this.key_board_control = false;
+ }
+
+ public void set_highlighted_slice(int index) {
+ if (index != this.active_slice) {
+ if (index >= 0 && index < this.slice_count())
+ this.active_slice = index;
+ else if (this.quick_action >= 0)
+ this.active_slice = this.quick_action;
+ else
+ this.active_slice = -1;
+
+ SliceRenderer? active = (this.active_slice >= 0 && this.active_slice < this.slice_count()) ?
+ this.slices[this.active_slice] : null;
+
+ center.set_active_slice(active);
+
+ foreach (var slice in this.slices)
+ slice.set_active_slice(active);
+
+ this.key_board_control = true;
+ }
+ }
+}
+
+}
diff --git a/src/renderers/pieWindow.vala b/src/renderers/pieWindow.vala
new file mode 100644
index 0000000..c4ac2ec
--- /dev/null
+++ b/src/renderers/pieWindow.vala
@@ -0,0 +1,263 @@
+/*
+Copyright (c) 2011 by Simon Schneegans
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+// An invisible window. Used to draw Pies onto.
+
+public class PieWindow : Gtk.Window {
+
+ public signal void on_closing();
+
+ private PieRenderer renderer;
+ private bool closing = false;
+ private GLib.Timer timer;
+
+ private bool has_compositing = false;
+
+ private Image background = null;
+
+ public PieWindow() {
+ this.renderer = new PieRenderer();
+
+ this.set_title("Gnome-Pie");
+ this.set_skip_taskbar_hint(true);
+ this.set_skip_pager_hint(true);
+ this.set_keep_above(true);
+ this.set_type_hint(Gdk.WindowTypeHint.SPLASHSCREEN);
+ this.set_decorated(false);
+ this.set_resizable(false);
+ this.icon_name = "gnome-pie";
+ this.set_accept_focus(false);
+
+ if (this.screen.is_composited()) {
+ this.set_colormap(this.screen.get_rgba_colormap());
+ this.has_compositing = true;
+ }
+
+ this.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK |
+ Gdk.EventMask.KEY_RELEASE_MASK |
+ Gdk.EventMask.KEY_PRESS_MASK |
+ Gdk.EventMask.POINTER_MOTION_MASK);
+
+ this.button_release_event.connect ((e) => {
+ if (e.button == 1) this.activate_slice();
+ else this.cancel();
+ return true;
+ });
+
+ // remember last pressed key in order to disable key repeat
+ uint last_key = 0;
+ this.key_press_event.connect((e) => {
+ if (e.keyval != last_key) {
+ last_key = e.keyval;
+ this.handle_key_press(e.keyval);
+ }
+ return true;
+ });
+
+ this.key_release_event.connect((e) => {
+ last_key = 0;
+ if (Config.global.turbo_mode)
+ this.activate_slice();
+ else
+ this.handle_key_release(e.keyval);
+ return true;
+ });
+
+ this.motion_notify_event.connect((e) => {
+ this.renderer.on_mouse_move();
+ return true;
+ });
+
+ this.expose_event.connect(this.draw);
+ }
+
+ public void load_pie(Pie pie) {
+ this.renderer.load_pie(pie);
+ this.set_window_position();
+ this.set_size_request(renderer.get_size(), renderer.get_size());
+ }
+
+ public void open() {
+ this.realize();
+
+ if (!this.has_compositing) {
+ int x, y, width, height;
+ this.get_position(out x, out y);
+ this.get_size(out width, out height);
+ this.background = new Image.capture_screen(x, y, width+1, height+1);
+ }
+
+ this.show();
+ this.fix_focus();
+
+ this.timer = new GLib.Timer();
+ this.timer.start();
+ this.queue_draw();
+
+ Timeout.add((uint)(1000.0/Config.global.refresh_rate), () => {
+ this.queue_draw();
+ return this.visible;
+ });
+ }
+
+ private bool draw(Gtk.Widget da, Gdk.EventExpose event) {
+ // clear the window
+ var ctx = Gdk.cairo_create(this.window);
+
+ if (this.has_compositing) {
+ ctx.set_operator (Cairo.Operator.CLEAR);
+ ctx.paint();
+ ctx.set_operator (Cairo.Operator.OVER);
+ } else {
+ ctx.set_operator (Cairo.Operator.OVER);
+ ctx.set_source_surface(background.surface, -1, -1);
+ ctx.paint();
+ }
+
+ ctx.translate(this.width_request*0.5, this.height_request*0.5);
+
+ double mouse_x = 0.0, mouse_y = 0.0;
+ this.get_pointer(out mouse_x, out mouse_y);
+
+ double frame_time = this.timer.elapsed();
+ this.timer.reset();
+
+ this.renderer.draw(frame_time, ctx, (int)(mouse_x - this.width_request*0.5),
+ (int)(mouse_y - this.height_request*0.5));
+
+ return true;
+ }
+
+ private void activate_slice() {
+ if (!this.closing) {
+ this.closing = true;
+ this.on_closing();
+ this.unfix_focus();
+ this.renderer.activate();
+
+ Timeout.add((uint)(Config.global.theme.fade_out_time*1000), () => {
+ this.destroy();
+ //ThemedIcon.clear_cache();
+ return false;
+ });
+ }
+ }
+
+ private void cancel() {
+ if (!this.closing) {
+ this.closing = true;
+ this.on_closing();
+ this.unfix_focus();
+ this.renderer.cancel();
+
+ Timeout.add((uint)(Config.global.theme.fade_out_time*1000), () => {
+ this.destroy();
+ //ThemedIcon.clear_cache();
+ return false;
+ });
+ }
+ }
+
+ private void set_window_position() {
+ if(Config.global.open_at_mouse) this.set_position(Gtk.WindowPosition.MOUSE);
+ else this.set_position(Gtk.WindowPosition.CENTER);
+ }
+
+ private void handle_key_press(uint key) {
+ if (Gdk.keyval_name(key) == "Escape") this.cancel();
+ else if (Gdk.keyval_name(key) == "Return") this.activate_slice();
+ else if (!Config.global.turbo_mode) {
+ if (Gdk.keyval_name(key) == "Up") this.renderer.select_up();
+ else if (Gdk.keyval_name(key) == "Down") this.renderer.select_down();
+ else if (Gdk.keyval_name(key) == "Left") this.renderer.select_left();
+ else if (Gdk.keyval_name(key) == "Right") this.renderer.select_right();
+ else if (Gdk.keyval_name(key) == "Alt_L") this.renderer.show_hotkeys = true;
+ else {
+ int index = -1;
+
+ if (key >= 48 && key <= 57) index = (int)key - 48;
+ else if (key >= 97 && key <= 122) index = (int)key - 87;
+ else if (key >= 65 && key <= 90) index = (int)key - 55;
+
+ if (index >= 0 && index < this.renderer.slice_count()) {
+ this.renderer.set_highlighted_slice(index);
+
+ if (this.renderer.active_slice == index) {
+ GLib.Timeout.add((uint)(Config.global.theme.transition_time*1000.0), ()=> {
+ this.activate_slice();
+ return false;
+ });
+ }
+
+ }
+ }
+ }
+ }
+
+ private void handle_key_release(uint key) {
+ if (!Config.global.turbo_mode) {
+ if (Gdk.keyval_name(key) == "Alt_L") this.renderer.show_hotkeys = false;
+ }
+ }
+
+ // utilities for grabbing focus
+ // Code from Gnome-Do/Synapse
+ private void fix_focus() {
+ uint32 timestamp = Gtk.get_current_event_time();
+ this.present_with_time(timestamp);
+ this.get_window().raise();
+ this.get_window().focus(timestamp);
+
+ int i = 0;
+ Timeout.add(100, () => {
+ if (++i >= 100) return false;
+ return !try_grab_window();
+ });
+ }
+
+ // Code from Gnome-Do/Synapse
+ private void unfix_focus() {
+ uint32 time = Gtk.get_current_event_time();
+ Gdk.pointer_ungrab(time);
+ Gdk.keyboard_ungrab(time);
+ Gtk.grab_remove(this);
+ }
+
+ // Code from Gnome-Do/Synapse
+ private bool try_grab_window() {
+ uint time = Gtk.get_current_event_time();
+ if (Gdk.pointer_grab(this.get_window(), true, Gdk.EventMask.BUTTON_PRESS_MASK |
+ Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK,
+ null, null, time) == Gdk.GrabStatus.SUCCESS) {
+
+ if (Gdk.keyboard_grab(this.get_window(), true, time) == Gdk.GrabStatus.SUCCESS) {
+ Gtk.grab_add(this);
+ return true;
+ } else {
+ Gdk.pointer_ungrab(time);
+ return false;
+ }
+ }
+ return false;
+ }
+}
+
+}
diff --git a/src/renderers/sliceRenderer.vala b/src/renderers/sliceRenderer.vala
new file mode 100644
index 0000000..08c880f
--- /dev/null
+++ b/src/renderers/sliceRenderer.vala
@@ -0,0 +1,182 @@
+/*
+Copyright (c) 2011 by Simon Schneegans
+
+This program is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using GLib.Math;
+
+namespace GnomePie {
+
+// Renders a Slice of a Pie. According to the current theme.
+
+public class SliceRenderer : GLib.Object {
+
+ public bool active {get; private set; default = false;}
+ public Image caption {get; private set;}
+ public Color color {get; private set;}
+
+ private Image active_icon;
+ private Image inactive_icon;
+ private Image hotkey;
+
+ private Action action;
+
+ private unowned PieRenderer parent;
+ private int position;
+
+ private AnimatedValue fade;
+ private AnimatedValue scale;
+ private AnimatedValue alpha;
+ private AnimatedValue fade_rotation;
+ private AnimatedValue fade_scale;
+
+ public SliceRenderer(PieRenderer parent) {
+ this.parent = parent;
+
+ this.fade = new AnimatedValue.linear(0.0, 0.0, Config.global.theme.transition_time);
+ this.alpha = new AnimatedValue.linear(0.0, 1.0, Config.global.theme.fade_in_time);
+ this.scale = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,
+ 1.0/Config.global.theme.max_zoom,
+ 1.0/Config.global.theme.max_zoom,
+ Config.global.theme.transition_time,
+ Config.global.theme.springiness);
+ this.fade_scale = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,
+ Config.global.theme.fade_in_zoom, 1.0,
+ Config.global.theme.fade_in_time,
+ Config.global.theme.springiness);
+ this.fade_rotation = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,
+ Config.global.theme.fade_in_rotation, 0.0,
+ Config.global.theme.fade_in_time);
+ }
+
+ public void load(Action action, int position) {
+ this.position = position;
+ this.action = action;
+
+
+ if (Config.global.theme.caption)
+ this.caption = new RenderedText(action.name,
+ Config.global.theme.caption_width,
+ Config.global.theme.caption_height,
+ Config.global.theme.caption_font);
+
+ this.active_icon = new ThemedIcon(action.icon, true);
+ this.inactive_icon = new ThemedIcon(action.icon, false);
+
+ this.color = new Color.from_icon(this.active_icon);
+
+ string hotkey_label = "";
+ if (position < 10) {
+ hotkey_label = "%u".printf(position);
+ } else if (position < 36) {
+ hotkey_label = "%c".printf((char)(55 + position));
+ }
+
+ this.hotkey = new RenderedText(hotkey_label, (int)Config.global.theme.slice_radius*2,
+ (int)Config.global.theme.slice_radius*2, "sans 20");
+ }
+
+ public void activate() {
+ action.activate();
+ }
+
+ public void fade_out() {
+ this.alpha.reset_target(0.0, Config.global.theme.fade_out_time);
+ this.fade_scale = new AnimatedValue.cubic(AnimatedValue.Direction.IN,
+ this.fade_scale.val,
+ Config.global.theme.fade_out_zoom,
+ Config.global.theme.fade_out_time,
+ Config.global.theme.springiness);
+ this.fade_rotation = new AnimatedValue.cubic(AnimatedValue.Direction.IN,
+ this.fade_rotation.val,
+ Config.global.theme.fade_out_rotation,
+ Config.global.theme.fade_out_time);
+ }
+
+ public void set_active_slice(SliceRenderer? active_slice) {
+ if (active_slice == this) {
+ this.fade.reset_target(1.0, Config.global.theme.transition_time);
+ } else {
+ this.fade.reset_target(0.0, Config.global.theme.transition_time);
+ }
+ }
+
+ public void draw(double frame_time, Cairo.Context ctx, double angle, double distance) {
+
+ double direction = 2.0 * PI * position/parent.slice_count() + this.fade_rotation.val;
+ double max_scale = 1.0/Config.global.theme.max_zoom;
+ double diff = fabs(angle-direction);
+
+ if (diff > PI)
+ diff = 2 * PI - diff;
+
+ if (diff < 2 * PI * Config.global.theme.zoom_range)
+ max_scale = (Config.global.theme.max_zoom/(diff * (Config.global.theme.max_zoom - 1)
+ /(2 * PI * Config.global.theme.zoom_range) + 1))
+ /Config.global.theme.max_zoom;
+
+ active = ((parent.active_slice >= 0) && (diff < PI/parent.slice_count()));
+
+ max_scale = (parent.active_slice >= 0 ? max_scale : 1.0/Config.global.theme.max_zoom);
+
+ if (fabs(this.scale.end - max_scale) > Config.global.theme.max_zoom*0.005)
+ this.scale.reset_target(max_scale, Config.global.theme.transition_time);
+
+ this.scale.update(frame_time);
+ this.alpha.update(frame_time);
+ this.fade.update(frame_time);
+ this.fade_scale.update(frame_time);
+ this.fade_rotation.update(frame_time);
+
+ ctx.save();
+
+ double radius = Config.global.theme.radius;
+
+ if (atan((Config.global.theme.slice_radius+Config.global.theme.slice_gap)
+ /(radius/Config.global.theme.max_zoom)) > PI/parent.slice_count()) {
+ radius = (Config.global.theme.slice_radius+Config.global.theme.slice_gap)
+ /tan(PI/parent.slice_count())*Config.global.theme.max_zoom;
+ }
+
+ ctx.scale(scale.val*fade_scale.val, scale.val*fade_scale.val);
+ ctx.translate(cos(direction)*radius, sin(direction)*radius);
+
+ ctx.push_group();
+
+ ctx.set_operator(Cairo.Operator.ADD);
+
+ if (fade.val > 0.0) active_icon.paint_on(ctx, this.alpha.val*this.fade.val);
+ if (fade.val < 1.0) inactive_icon.paint_on(ctx, this.alpha.val*(1.0 - fade.val));
+
+ if (this.parent.show_hotkeys) {
+ ctx.set_operator(Cairo.Operator.ATOP);
+ ctx.set_source_rgba(0, 0, 0, 0.5);
+ ctx.paint();
+ }
+
+ ctx.set_operator(Cairo.Operator.OVER);
+
+
+ ctx.pop_group_to_source();
+ ctx.paint();
+
+ if (this.parent.show_hotkeys)
+ this.hotkey.paint_on(ctx, 1.0);
+
+ ctx.restore();
+ }
+}
+
+}