5 # Copyright (c) 2006 Tilman Sauerbeck (tilman at code-monkey de)
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of version 2 of the GNU General Public License as
9 # published by the Free Software Foundation.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
26 require "embrace/imap"
30 DATADIR = "#{PREFIX || "/usr/local"}/share/#{PKG_NAME}/"
34 ICON_FILE = DATADIR + "l33t_MAI_envelope.png"
36 class ZeroToOneAnimator < Ecore::Animator
37 def initialize(duration)
43 v = [(Time.now - started) / duration, 1.0].min
47 @finished.call(self) unless (v < 1.0) || @finished.nil?
58 def on_finished(&block)
63 # an animator that runs for the specified number of seconds,
64 # and yields values between 0 and 255
65 class AlphaAnimator < ZeroToOneAnimator
66 def initialize(duration, object)
67 super(duration) do |v|
69 object.set_color(a, a, a, a)
78 class InverseAlphaAnimator < AlphaAnimator
84 class MoveAnimator < ZeroToOneAnimator
85 def initialize(duration, movement, *objects)
86 zipped = objects.zip(objects.map { |o| o.geometry[0..1] })
88 super(duration) do |v|
90 v = Math.sin(v * Math::PI / 2.0)
92 zipped.each do |(o, orig_pos)|
93 o.move(orig_pos.first,
94 orig_pos.last + (movement * v).to_i)
100 class MailboxIcon < Evas::Smart
101 class FadeOutFinishedEvent < Ecore::Event
113 def initialize(evas, label)
121 @img = Evas::Image.new(evas)
122 @label = Evas::Text.new(evas)
124 @objects = [@img, @label]
125 @objects.each { |o| add_member(o) }
127 set_color(0, 0, 0, 0)
129 @img.set_file(ICON_FILE)
130 @img.set_fill(0, 0, *@img.get_size)
133 @label.set_font("VeraBd", 10)
138 @label_offset_x = (b[0] / 2) - (a[2] / 2)
139 @label_offset_y = (b[1] / 2) - (a[3] / 2)
141 resize(*@img.get_size)
147 @alpha_anim ||= AlphaAnimator.new(2, self)
148 @alpha_anim.on_finished { @alpha_anim = nil }
152 @alpha_anim ||= InverseAlphaAnimator.new(2, self)
153 @alpha_anim.on_finished do
155 FadeOutFinishedEvent.raise(self)
161 @objects.each { |o| o.show }
165 @objects.each { |o| o.hide }
167 @alpha_anim && @alpha_anim.delete
172 @objects.each { |o| o.delete }
175 @alpha_anim && @alpha_anim.delete
177 @img = @label = @alpha_anim = nil
183 # center the label on the image
184 @label.move(x + @label_offset_x,
188 def smart_resize(w, h)
192 def smart_color_set(r, g, b, a)
193 @img.set_color(r, g, b, a)
194 @label.set_color(r, 0, 0, a)
198 class Container < Evas::Smart
199 class ContainerError < StandardError; end
200 class ContainerFullError < ContainerError; end
201 class ContainerLockedError < ContainerError; end
206 @bg = Evas::Rectangle.new(evas)
207 @bg.set_color(0, 0, 0, 0)
218 Ecore::EventHandler.new(MailboxIcon::FadeOutFinishedEvent,
219 &method(:on_icon_fade_out_finished))
224 !slots_left.zero? && @add_lock_count.zero?
228 @about_to_add.zero? && @add_lock_count.zero?
232 Kernel.raise(ContainerFullError) if slots_left.zero?
233 Kernel.raise(ContainerLockedError) unless @add_lock_count.zero?
238 geo[1] + geo[3] % Main.instance.icon_height)
244 # check whether we need to need to move this icon
250 movement = Main.instance.icon_height * (slots_left - 1)
254 move_time = 0.25 * slots_left
255 @animators << MoveAnimator.new(move_time, movement, i)
257 @animators.last.on_finished do |ani|
258 @animators.delete(ani)
266 i = @icons.index(icon)
267 return (block_given? ? yield : nil) if i.nil?
273 Kernel.raise(ContainerLockedError) unless @about_to_add.zero?
274 Kernel.raise(ContainerLockedError) unless @add_lock_count.zero?
280 def on_icon_fade_out_finished(ev)
281 i = @icons.index(ev.icon)
285 # icons that are placed above the one that's deleted need
286 # to be moved. check whether are there any first
287 if i == @icons.length
294 @animators << MoveAnimator.new(2, Main.instance.icon_height, *ar)
295 @animators.last.on_finished do |ani|
296 @animators.delete(ani)
319 def smart_resize(w, h)
325 geometry.pop / Main.instance.icon_height
329 max_icons - @icons.nitems - @about_to_add
333 @icons.nitems + @about_to_add
337 class Main < Ecore::Evas::SoftwareX11
340 attr_reader :container
345 self.has_alpha = true
346 self.title = "Embrace"
347 self.borderless = true
349 @icon_dim = IO.read(ICON_FILE, 8, 16).unpack("NN")
351 on_resize { @container.resize(*geometry[2, 3]) }
353 @container = Container.new(evas)
354 @container.move(0, 0)
355 @container.layer = -1
358 size = [@icon_dim.first]
362 size << icon_height * 11
371 evas.font_path_append("/usr/lib/X11/fonts/TTF")
374 Ecore::EventHandler.new(IMAP::MailboxStatusEvent,
375 &method(:on_mailbox_status)),
376 Ecore::EventHandler.new(IMAP::FinishedEvent,
377 &method(:on_finished))
380 s = File.expand_path("~/.e/apps/embrace/config.yaml")
381 @config = YAML.load(File.read(s))
384 @timer = Ecore::Timer.new(30, &method(:on_timer))
393 Ecore.main_loop_begin
398 @server ||= IMAP::Session.new(@config)
403 def on_mailbox_status(ev)
404 md = ev.name.match(/^Lists.(.+)$/)
408 lbl = md.captures.first
411 found = evas.find_object(lbl)
413 if ev.count.zero? && !found.nil? && @container.can_delete?
414 @container.delete(found)
415 elsif !ev.count.zero? && found.nil? && @container.can_add?
416 @container << MailboxIcon.new(evas, lbl)
428 Embrace::Main.instance.show
429 Embrace::Main.instance.run