71f982307abe1f2d2ca4a0647971f3dee7ae5f09
[redact.git] / lib / redact / part.rb
1 #--
2 # $Id: part.rb 5 2005-03-26 20:23:02Z tilman $
3 #
4 # Copyright (c) 2005 Tilman Sauerbeck (tilman at code-monkey de)
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining
7 # a copy of this software and associated documentation files (the
8 # "Software"), to deal in the Software without restriction, including
9 # without limitation the rights to use, copy, modify, merge, publish,
10 # distribute, sublicense, and/or sell copies of the Software, and to
11 # permit persons to whom the Software is furnished to do so, subject to
12 # the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be
15 # included in all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25 module Redact
26         class Part
27                 TYPE_RECTANGLE = 1
28                 TYPE_TEXT = 2
29                 TYPE_IMAGE = 3
30                 TYPE_SWALLOW = 4
31
32                 include Comparable
33
34                 attr_reader :collection, :id, :name, :dragable, :clip
35                 attr_accessor :mouse_events, :repeat_events
36
37                 def initialize(collection, id, name)
38                         @collection = collection
39                         @id = id
40
41                         @name = name.to_str.dup.freeze
42                         @type = TYPE_RECTANGLE
43                         @mouse_events = true
44                         @repeat_events = false
45                         @clip = nil
46                         @dragable = Dragable.new(self)
47
48                         @descriptions = Hash.new do |h, k|
49                                 desc, value = k.split("\0")
50                                 value = value.to_f
51
52                                 h[k] = description_class.new(desc, value)
53                         end
54                 end
55
56                 def <=>(b)
57                         @id <=> b.id
58                 end
59
60                 def clip=(part)
61                         if self == part
62                                 raise(ArgumentError, "cannot clip part to itself")
63                         elsif part.nil?
64                                 @clip = nil
65                         elsif part.collection != @collection
66                                 raise(ArgumentError, "parts' collections not identical")
67                         else
68                                 @clip = part
69                         end
70                 end
71
72                 def description(name = "default", value = 0.0) # :yields: desc
73                         d = @descriptions[desc_key(name, value)]
74
75                         block_given? ? (yield d) : d
76                 end
77
78                 protected
79                 def description_class
80                         Description
81                 end
82
83                 def to_eet_name
84                         "Edje_Part"
85                 end
86
87                 def to_eet_properties
88                         other_desc = @descriptions.dup
89                         other_desc.delete(desc_key("default", 0.0))
90
91                         confine_id = @dragable.confine.nil? ?
92                                          -1 : @dragable.confine.id
93
94                         {"name" => [@name],
95                          "id" => [@id],
96                          "type" => [@type, :char],
97                          "effect" => [0, :char],
98                          "mouse_events" => [@mouse_events],
99                          "repeat_events" => [@repeat_events],
100                          "clip_to_id" => [@clip.nil? ? -1 : @clip.id],
101                          "default_desc" => [description("default", 0.0)],
102                          "other_desc" => [other_desc],
103                          "dragable.x" => [@dragable.enabled[0], :char],
104                          "dragable.step_x" => [@dragable.step[0]],
105                          "dragable.count_x" => [@dragable.count[0]],
106                          "dragable.y" => [@dragable.enabled[1], :char],
107                          "dragable.step_y" => [@dragable.step[1]],
108                          "dragable.count_y" => [@dragable.count[1]],
109                          "dragable.counfine_id" => [confine_id]} # not a typo!
110                 end
111
112                 private
113                 def desc_key(name, value)
114                         name + "\0" + value.to_s
115                 end
116         end
117
118         class SwallowPart < Part
119                 def initialize(collection, id, name)
120                         super
121
122                         @type = TYPE_SWALLOW
123                 end
124         end
125
126         class TextPart < Part
127                 EFFECT_NONE = 0
128                 EFFECT_PLAIN = 1
129                 EFFECT_OUTLINE = 2
130                 EFFECT_SOFT_OUTLINE = 3
131                 EFFECT_SHADOW = 4
132                 EFFECT_SOFT_SHADOW = 5
133                 EFFECT_OUTLINE_SHADOW = 6
134                 EFFECT_OUTLINE_SOFT_SHADOW = 7
135
136                 attr_accessor :effect
137
138                 def initialize(collection, id, name)
139                         super
140
141                         @type = TYPE_TEXT
142                         @effect = EFFECT_NONE
143                 end
144
145                 protected
146                 def description_class
147                         TextDescription
148                 end
149
150                 def to_eet_properties
151                         super.merge!({"effect" => [@effect, :char]})
152                 end
153         end
154
155         class ImagePart < Part
156                 def initialize(collection, id, name)
157                         super
158
159                         @type = TYPE_IMAGE
160                 end
161
162                 protected
163                 def description_class
164                         ImageDescription
165                 end
166         end
167
168         class Dragable
169                 attr_reader :enabled, :step, :count, :confine
170
171                 def initialize(part)
172                         @part= part
173
174                         @enabled = [false, false]
175                         @step = [0, 0]
176                         @count = [0, 0]
177                         @confine = nil
178                 end
179
180                 def confine=(part)
181                         if part == self
182                                 raise(ArgumentError, "cannot confine part to itself")
183                         elsif !part.nil? && part.collection != @part.collection
184                                 raise(ArgumentError, "parts' collections not identical")
185                         else
186                                 @confine = part
187                         end
188                 end
189         end
190
191         class Relation
192                 attr_reader :rel, :to_id, :offset
193
194                 def initialize(rel, offset)
195                         @rel = [rel, rel]
196                         @to_id = [-1, -1]
197                         @offset = [offset, offset]
198                 end
199
200                 def set_rel(x, y)
201                         @rel = [x, y]
202                 end
203
204                 def set_offset(x, y)
205                         @offset = [x, y]
206                 end
207
208                 def to=(part)
209                         @to_id = [].fill(part.nil? ? -1 : part.id, 0..1)
210                 end
211         end
212
213         class Description
214                 attr_reader :rel
215                 attr_accessor :visible
216
217                 def initialize(name = "default", value = 0.0)
218                         @name = name.to_str.dup.freeze
219                         @value = value.freeze
220                         @visible = true
221                         @align = [0.5, 0.5]
222                         @min = [0, 0]
223                         @max = [-1, -1]
224                         @step_x = 0
225                         @step_y = 0
226                         @rel = [Relation.new(0.0, 0), Relation.new(1.0, -1)]
227                         @color = [].fill(255, 0..3)
228                         @color_class = ""
229                 end
230
231                 def set_align(x = 0.5, y = 0.5)
232                         @align = [x, y]
233                 end
234
235                 def set_size(w, h)
236                         set_min(w, h)
237                         set_max(w, h)
238                 end
239
240                 def set_min(w, h)
241                         @min = [w, h]
242                 end
243
244                 def set_max(w, h)
245                         @max = [w, h]
246                 end
247
248                 def color=(c)
249                         @color = parse_hex_color(c)
250                 end
251
252                 protected
253                 def parse_hex_color(c)
254                         md = c.match(/^#?(([[:xdigit:]]{2}){1,4})$/)
255                         if md.nil?
256                                 raise(ArgumentError, "Argument is not a hex string")
257                         end
258
259                         pairs = md.captures.shift.split(/(..)/).delete_if do |item|
260                                 item == ""
261                         end
262
263                         pairs.push("00") while pairs.length < 3
264                         pairs.push("ff") if pairs.length == 3
265
266                         pairs.map { |p| p.hex }
267                 end
268
269                 def to_eet_name
270                         "Edje_Part_Description"
271                 end
272
273                 def to_eet_properties
274                         {"state.name" => [@name],
275                          "state.value" => [@value, :double],
276                          "visible" => [@visible],
277                          "align.x" => [@align[0], :double],
278                          "align.y" => [@align[1], :double],
279                          "min.w" => [@min[0]],
280                          "min.h" => [@min[1]],
281                          "max.w" => [@max[0]],
282                          "max.h" => [@max[1]],
283                          "step.x" => [@step_x],
284                          "step.y" => [@step_y],
285                          "aspect.min" => [0.0, :double],
286                          "aspect.max" => [0.0, :double],
287                          "aspect.prefer" => [0, :char],
288                          "rel1.relative_x" => [@rel[0].rel[0], :double],
289                          "rel1.relative_y" => [@rel[0].rel[1], :double],
290                          "rel1.offset_x" => [@rel[0].offset[0]],
291                          "rel1.offset_y" => [@rel[0].offset[1]],
292                          "rel1.id_x" => [@rel[0].to_id[0]],
293                          "rel1.id_y" => [@rel[0].to_id[1]],
294                          "rel2.relative_x" => [@rel[1].rel[0], :double],
295                          "rel2.relative_y" => [@rel[1].rel[1], :double],
296                          "rel2.offset_x" => [@rel[1].offset[0]],
297                          "rel2.offset_y" => [@rel[1].offset[1]],
298                          "rel2.id_x" => [@rel[1].to_id[0]],
299                          "rel2.id_y" => [@rel[1].to_id[1]],
300                          "color_class" => [@color_class],
301                          "color.r" => [@color[0], :char],
302                          "color.g" => [@color[1], :char],
303                          "color.b" => [@color[2], :char],
304                          "color.a" => [@color[3], :char],
305
306                          # image properties
307                          "image.id" => [-1],
308                          "image.tween_list" => [nil],
309                          "border.l" => [0],
310                          "border.r" => [0],
311                          "border.t" => [0],
312                          "border.b" => [0],
313                          "fill.smooth" => [true],
314                          "fill.pos_rel_x" => [0.0, :double],
315                          "fill.pos_abs_x" => [0],
316                          "fill.rel_x" => [1.0, :double],
317                          "fill.abs_x" => [0],
318                          "fill.pos_rel_y" => [0.0, :double],
319                          "fill.pos_abs_y" => [0],
320                          "fill.rel_y" => [1.0, :double],
321                          "fill.abs_y" => [0],
322
323                          # text properties
324                          "color2.r" => [0, :char],
325                          "color2.g" => [0, :char],
326                          "color2.b" => [0, :char],
327                          "color2.a" => [255, :char],
328                          "color3.r" => [0, :char],
329                          "color3.g" => [0, :char],
330                          "color3.b" => [0, :char],
331                          "color3.a" => [128, :char],
332                          "text.text" => [""],
333                          "text.text_class" => [""],
334                          "text.font" => [""],
335                          "text.size" => [0],
336                          "text.fit_x" => [false],
337                          "text.fit_y" => [false],
338                          "text.min_x" => [0],
339                          "text.min_y" => [0],
340                          "text.align.x" => [0.0, :double],
341                          "text.align.y" => [0.0, :double],
342                          "text.id_source" => [-1],
343                          "text.id_text_source" => [-1]}
344                 end
345         end
346
347         class Tween
348                 def initialize(image)
349                         @id = image.id
350                 end
351
352                 def to_eet_name
353                         "Edje_Part_Image_Id"
354                 end
355         end
356
357         class Tweens < Array
358                 def <<(im)
359                         image = EDJE.image_dir.find { |e| e.filename == im }
360                         if image.nil?
361                                 image = ImageDirectoryEntry.new(im)
362                                 EDJE.image_dir << image
363                         end
364
365                         super(Tween.new(image))
366                 end
367         end
368
369         class ImageDescription < Description
370                 attr_reader :image, :auto_rel, :tweens
371
372                 def initialize(name = "default", value = 0.0)
373                         super
374
375                         @image = nil
376                         @tweens = Tweens.new
377                         @border = [0, 0, 0, 0]
378                         @fill_smooth = true
379                         @auto_rel = false
380                 end
381
382                 def image=(im)
383                         return if !@image.nil? && im == @image.filename
384
385                         @image = EDJE.image_dir.find { |e| e.filename == im }
386                         if @image.nil?
387                                 @image = ImageDirectoryEntry.new(im)
388                                 EDJE.image_dir << @image
389                         end
390
391                         self.auto_rel = @auto_rel
392                 end
393
394                 def auto_rel=(b)
395                         @auto_rel = b
396
397                         if @auto_rel && !@image.nil?
398                                 off = @rel[0].offset
399
400                                 @rel[1].set_rel(0.0, 0.0)
401                                 @rel[1].set_offset(off[0] + @image.image.width - 1,
402                                                    off[1] + @image.image.height - 1)
403                         end
404                 end
405
406                 def set_border(l = 0, r = 0, t = 0, b = 0)
407                         @border = [r, r, t, b]
408                 end
409
410                 def to_eet_properties
411                         super.merge!(
412                         {"image.id" => [@image.nil? ? -1 : @image.id],
413                          "image.tween_list" => [@tweens],
414                          "border.l" => [@border[0]],
415                          "border.r" => [@border[1]],
416                          "border.t" => [@border[2]],
417                          "border.b" => [@border[3]],
418                          "fill.smooth" => [@fill_smooth],
419                          "fill.pos_rel_x" => [0.0, :double],
420                          "fill.pos_abs_x" => [0],
421                          "fill.rel_x" => [1.0, :double],
422                          "fill.abs_x" => [0],
423                          "fill.pos_rel_y" => [0.0, :double],
424                          "fill.pos_abs_y" => [0],
425                          "fill.rel_y" => [1.0, :double],
426                          "fill.abs_y" => [0]})
427                 end
428         end
429
430         class TextDescription < Description
431                 attr_reader :font
432                 attr_accessor :text, :font_size
433
434                 def initialize(name = "default", value = 0.0)
435                         super
436
437                         @outline_color = [0, 0, 0, 255]
438                         @shadow_color = [0, 0, 0, 128]
439                         @text = ""
440                         @text_class = ""
441                         @font = ""
442                         @font_size = 0
443                         @fit = [false, false]
444                         @text_min = [false, false]
445                         @text_align = [0.5, 0.5]
446                         @text_id_source = -1
447                         @text_id_text_source = -1
448                 end
449
450                 def set_fit(x = false, y = false)
451                         @fit = [x, y]
452                 end
453
454                 def set_text_min(x = false, y = false)
455                         @text_min = [x, y]
456                 end
457
458                 def set_text_align(x = 0.5, y = 0.5)
459                         @text_align = [x, y]
460                 end
461
462                 def font=(f)
463                         md = f.to_str.match(/.*\.ttf/)
464                         unless md.nil?
465                                 found = EDJE.font_dir.find { |font| font.filename == f }
466                                 if found.nil?
467                                         EDJE.font_dir << FontDirectoryEntry.new(f)
468                                 end
469                         end
470
471                         @font = f.dup
472                 end
473
474                 def outline_color=(c)
475                         @outline_color = parse_hex_color(c)
476                 end
477
478                 def shadow_color=(c)
479                         @shadow_color = parse_hex_color(c)
480                 end
481
482                 def to_eet_properties
483                         super.merge!(
484                         {"color2.r" => [@outline_color[0], :char],
485                          "color2.g" => [@outline_color[1], :char],
486                          "color2.b" => [@outline_color[2], :char],
487                          "color2.a" => [@outline_color[3], :char],
488                          "color3.r" => [@shadow_color[0], :char],
489                          "color3.g" => [@shadow_color[1], :char],
490                          "color3.b" => [@shadow_color[2], :char],
491                          "color3.a" => [@shadow_color[3], :char],
492                          "text.text" => [@text],
493                          "text.text_class" => [@text_class],
494                          "text.font" => [@font],
495                          "text.size" => [@font_size],
496                          "text.fit_x" => [@fit[0]],
497                          "text.fit_y" => [@fit[1]],
498                          "text.min_x" => [@text_min[0]],
499                          "text.min_y" => [@text_min[1]],
500                          "text.align.x" => [@text_align[0], :double],
501                          "text.align.y" => [@text_align[1], :double],
502                          "text.id_source" => [@text_id_source],
503                          "text.id_text_source" => [@text_id_text_source]})
504                 end
505         end
506 end