c6ce88dcd497e0c24b81511896886e7b2a0544d4
[redact.git] / lib / redact / part.rb
1 #--
2 # $Id: part.rb 21 2005-04-02 22:26:40Z 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 part == self
62                                 raise(ArgumentError, "cannot clip part to itself")
63                         elsif !part.nil? && part.collection != @collection
64                                 raise(ArgumentError, "items not in the same collection")
65                         else
66                                 @clip = part
67                         end
68                 end
69
70                 def description(name = "default", value = 0.0) # :yields: desc
71                         d = @descriptions[desc_key(name, value)]
72
73                         block_given? ? (yield d) : d
74                 end
75
76                 protected
77                 def description_class
78                         Description
79                 end
80
81                 def to_eet_name
82                         "Edje_Part"
83                 end
84
85                 def to_eet_properties
86                         other_desc = @descriptions.dup
87                         other_desc.delete(desc_key("default", 0.0))
88
89                         confine_id = @dragable.confine.nil? ?
90                                          -1 : @dragable.confine.id
91
92                         {"name" => [@name],
93                          "id" => [@id],
94                          "type" => [@type, :char],
95                          "effect" => [0, :char],
96                          "mouse_events" => [@mouse_events],
97                          "repeat_events" => [@repeat_events],
98                          "clip_to_id" => [@clip.nil? ? -1 : @clip.id],
99                          "default_desc" => [description("default", 0.0)],
100                          "other_desc" => [other_desc],
101                          "dragable.x" => [@dragable.enabled[0], :char],
102                          "dragable.step_x" => [@dragable.step[0]],
103                          "dragable.count_x" => [@dragable.count[0]],
104                          "dragable.y" => [@dragable.enabled[1], :char],
105                          "dragable.step_y" => [@dragable.step[1]],
106                          "dragable.count_y" => [@dragable.count[1]],
107                          "dragable.counfine_id" => [confine_id]} # not a typo!
108                 end
109
110                 private
111                 def desc_key(name, value)
112                         name + "\0" + value.to_s
113                 end
114         end
115
116         class SwallowPart < Part
117                 def initialize(collection, id, name)
118                         super
119
120                         @type = TYPE_SWALLOW
121                 end
122         end
123
124         class TextPart < Part
125                 attr_accessor :effect
126
127                 def initialize(collection, id, name)
128                         super
129
130                         @type = TYPE_TEXT
131                         @effect = :none
132                 end
133
134                 protected
135                 def description_class
136                         TextDescription
137                 end
138
139                 def to_eet_properties
140                         effect = case @effect
141                         when :none: 0
142                         when :plain: 1
143                         when :outline: 2
144                         when :soft_outline: 3
145                         when :shadow: 4
146                         when :soft_shadow: 5
147                         when :outline_shadow: 6
148                         when :outline_soft_shadow: 7
149                         else
150                                 raise(RedactError, "invalid effect value - #{@effect}")
151                         end
152
153                         super.merge!({"effect" => [effect, :char]})
154                 end
155         end
156
157         class ImagePart < Part
158                 def initialize(collection, id, name)
159                         super
160
161                         @type = TYPE_IMAGE
162                 end
163
164                 protected
165                 def description_class
166                         ImageDescription
167                 end
168         end
169
170         class Dragable
171                 attr_reader :enabled, :step, :count, :confine
172
173                 def initialize(part)
174                         @part= part
175
176                         @enabled = [false, false]
177                         @step = [0, 0]
178                         @count = [0, 0]
179                         @confine = nil
180                 end
181
182                 def confine=(part)
183                         if part == @part
184                                 raise(ArgumentError, "cannot confine part to itself")
185                         elsif !part.nil? && part.collection != @part.collection
186                                 raise(ArgumentError, "items not in the same collection")
187                         else
188                                 @confine = part
189                         end
190                 end
191         end
192
193         class Relation
194                 attr_reader :rel, :to_id, :offset
195
196                 def initialize(rel, offset)
197                         @rel = [rel, rel]
198                         @to_id = [-1, -1]
199                         @offset = [offset, offset]
200                 end
201
202                 def set_rel(x, y)
203                         @rel = [x, y]
204                 end
205
206                 def set_offset(x, y)
207                         @offset = [x, y]
208                 end
209
210                 def to=(part)
211                         @to_id = [].fill(part.nil? ? -1 : part.id, 0..1)
212                 end
213         end
214
215         class Description
216                 attr_reader :rel, :aspect, :step
217                 attr_accessor :visible, :aspect_preference, :color_class
218
219                 def initialize(name = "default", value = 0.0)
220                         @name = name.to_str.dup.freeze
221                         @value = value.freeze
222                         @visible = true
223                         @align = [0.5, 0.5]
224                         @min = [0, 0]
225                         @max = [-1, -1]
226                         @step = [0, 0]
227                         @aspect = [0.0, 0.0]
228                         @aspect_preference = :none
229                         @rel = [Relation.new(0.0, 0), Relation.new(1.0, -1)]
230                         @color = [].fill(255, 0..3)
231                         @color_class = ""
232                 end
233
234                 def set_step(x = 0, y = 0)
235                         @step = [x, y]
236                 end
237
238                 def set_aspect(x = 0.0, y = 0.0)
239                         @aspect = [x, y]
240                 end
241
242                 def set_align(x = 0.5, y = 0.5)
243                         @align = [x, y]
244                 end
245
246                 def set_size(w, h)
247                         set_min(w, h)
248                         set_max(w, h)
249                 end
250
251                 def set_min(w, h)
252                         @min = [w, h]
253                 end
254
255                 def set_max(w, h)
256                         @max = [w, h]
257                 end
258
259                 def color=(c)
260                         @color = parse_hex_color(c)
261                 end
262
263                 protected
264                 def parse_hex_color(c)
265                         md = c.match(/^#?(([[:xdigit:]]{2}){1,4})$/)
266                         if md.nil?
267                                 raise(ArgumentError, "Argument is not a hex string")
268                         end
269
270                         pairs = md.captures.shift.split(/(..)/).delete_if do |item|
271                                 item == ""
272                         end
273
274                         pairs.push("00") while pairs.length < 3
275                         pairs.push("ff") if pairs.length == 3
276
277                         pairs.map { |p| p.hex }
278                 end
279
280                 def map_aspect_preference
281                         case @aspect_preference
282                         when :none
283                                 0
284                         when :vertical
285                                 1
286                         when :horizontal
287                                 2
288                         when :both
289                                 3
290                         else
291                                 raise(RedactError, "invalid aspect preference value - " +
292                                       @aspect_preference.to_s)
293                         end
294                 end
295
296                 def to_eet_name
297                         "Edje_Part_Description"
298                 end
299
300                 def to_eet_properties
301                         {"state.name" => [@name],
302                          "state.value" => [@value, :double],
303                          "visible" => [@visible],
304                          "align.x" => [@align[0], :double],
305                          "align.y" => [@align[1], :double],
306                          "min.w" => [@min[0]],
307                          "min.h" => [@min[1]],
308                          "max.w" => [@max[0]],
309                          "max.h" => [@max[1]],
310                          "step.x" => [@step[0]],
311                          "step.y" => [@step[1]],
312                          "aspect.min" => [@aspect[0], :double],
313                          "aspect.max" => [@aspect[1], :double],
314                          "aspect.prefer" => [map_aspect_preference, :char],
315                          "rel1.relative_x" => [@rel[0].rel[0], :double],
316                          "rel1.relative_y" => [@rel[0].rel[1], :double],
317                          "rel1.offset_x" => [@rel[0].offset[0]],
318                          "rel1.offset_y" => [@rel[0].offset[1]],
319                          "rel1.id_x" => [@rel[0].to_id[0]],
320                          "rel1.id_y" => [@rel[0].to_id[1]],
321                          "rel2.relative_x" => [@rel[1].rel[0], :double],
322                          "rel2.relative_y" => [@rel[1].rel[1], :double],
323                          "rel2.offset_x" => [@rel[1].offset[0]],
324                          "rel2.offset_y" => [@rel[1].offset[1]],
325                          "rel2.id_x" => [@rel[1].to_id[0]],
326                          "rel2.id_y" => [@rel[1].to_id[1]],
327                          "color_class" => [@color_class],
328                          "color.r" => [@color[0], :char],
329                          "color.g" => [@color[1], :char],
330                          "color.b" => [@color[2], :char],
331                          "color.a" => [@color[3], :char],
332
333                          # image properties
334                          "image.id" => [-1],
335                          "image.tween_list" => [nil],
336                          "border.l" => [0],
337                          "border.r" => [0],
338                          "border.t" => [0],
339                          "border.b" => [0],
340                          "fill.smooth" => [true],
341                          "fill.pos_rel_x" => [0.0, :double],
342                          "fill.pos_abs_x" => [0],
343                          "fill.rel_x" => [1.0, :double],
344                          "fill.abs_x" => [0],
345                          "fill.pos_rel_y" => [0.0, :double],
346                          "fill.pos_abs_y" => [0],
347                          "fill.rel_y" => [1.0, :double],
348                          "fill.abs_y" => [0],
349
350                          # text properties
351                          "color2.r" => [0, :char],
352                          "color2.g" => [0, :char],
353                          "color2.b" => [0, :char],
354                          "color2.a" => [255, :char],
355                          "color3.r" => [0, :char],
356                          "color3.g" => [0, :char],
357                          "color3.b" => [0, :char],
358                          "color3.a" => [128, :char],
359                          "text.text" => [""],
360                          "text.text_class" => [""],
361                          "text.font" => [""],
362                          "text.size" => [0],
363                          "text.fit_x" => [false],
364                          "text.fit_y" => [false],
365                          "text.min_x" => [0],
366                          "text.min_y" => [0],
367                          "text.align.x" => [0.0, :double],
368                          "text.align.y" => [0.0, :double],
369                          "text.id_source" => [-1],
370                          "text.id_text_source" => [-1]}
371                 end
372         end
373
374         class Tween
375                 def initialize(image)
376                         @id = image.id
377                 end
378
379                 def to_eet_name
380                         "Edje_Part_Image_Id"
381                 end
382         end
383
384         class Tweens < Array
385                 def <<(im)
386                         image = EDJE.image_dir.find { |e| e.filename == im }
387                         if image.nil?
388                                 image = ImageDirectoryEntry.new(im)
389                                 EDJE.image_dir << image
390                         end
391
392                         super(Tween.new(image))
393                 end
394         end
395
396         class ImageDescription < Description
397                 attr_reader :image, :auto_rel, :tweens
398
399                 def initialize(name = "default", value = 0.0)
400                         super
401
402                         @image = nil
403                         @tweens = Tweens.new
404                         @border = [0, 0, 0, 0]
405                         @fill_smooth = true
406                         @auto_rel = false
407                 end
408
409                 def image=(im)
410                         return if !@image.nil? && im == @image.filename
411
412                         @image = EDJE.image_dir.find { |e| e.filename == im }
413                         if @image.nil?
414                                 @image = ImageDirectoryEntry.new(im)
415                                 EDJE.image_dir << @image
416                         end
417
418                         self.auto_rel = @auto_rel
419                 end
420
421                 def auto_rel=(b)
422                         @auto_rel = b
423
424                         if @auto_rel && !@image.nil?
425                                 off = @rel[0].offset
426
427                                 @rel[1].set_rel(0.0, 0.0)
428                                 @rel[1].set_offset(off[0] + @image.image.width - 1,
429                                                    off[1] + @image.image.height - 1)
430                         end
431                 end
432
433                 def set_border(l = 0, r = 0, t = 0, b = 0)
434                         @border = [r, r, t, b]
435                 end
436
437                 def to_eet_properties
438                         super.merge!(
439                         {"image.id" => [@image.nil? ? -1 : @image.id],
440                          "image.tween_list" => [@tweens],
441                          "border.l" => [@border[0]],
442                          "border.r" => [@border[1]],
443                          "border.t" => [@border[2]],
444                          "border.b" => [@border[3]],
445                          "fill.smooth" => [@fill_smooth],
446                          "fill.pos_rel_x" => [0.0, :double],
447                          "fill.pos_abs_x" => [0],
448                          "fill.rel_x" => [1.0, :double],
449                          "fill.abs_x" => [0],
450                          "fill.pos_rel_y" => [0.0, :double],
451                          "fill.pos_abs_y" => [0],
452                          "fill.rel_y" => [1.0, :double],
453                          "fill.abs_y" => [0]})
454                 end
455         end
456
457         class TextDescription < Description
458                 attr_reader :font
459                 attr_accessor :text, :font_size, :text_class
460
461                 def initialize(name = "default", value = 0.0)
462                         super
463
464                         @outline_color = [0, 0, 0, 255]
465                         @shadow_color = [0, 0, 0, 128]
466                         @text = ""
467                         @text_class = ""
468                         @font = ""
469                         @font_size = 0
470                         @fit = [false, false]
471                         @text_min = [false, false]
472                         @text_align = [0.5, 0.5]
473                         @text_id_source = -1
474                         @text_id_text_source = -1
475                 end
476
477                 def set_fit(x = false, y = false)
478                         @fit = [x, y]
479                 end
480
481                 def set_text_min(x = false, y = false)
482                         @text_min = [x, y]
483                 end
484
485                 def set_text_align(x = 0.5, y = 0.5)
486                         @text_align = [x, y]
487                 end
488
489                 def font=(f)
490                         md = f.to_str.match(/.*\.ttf/)
491                         unless md.nil?
492                                 found = EDJE.font_dir.find { |font| font.filename == f }
493                                 if found.nil?
494                                         EDJE.font_dir << FontDirectoryEntry.new(f)
495                                 end
496                         end
497
498                         @font = f.dup
499                 end
500
501                 def outline_color=(c)
502                         @outline_color = parse_hex_color(c)
503                 end
504
505                 def shadow_color=(c)
506                         @shadow_color = parse_hex_color(c)
507                 end
508
509                 def to_eet_properties
510                         super.merge!(
511                         {"color2.r" => [@outline_color[0], :char],
512                          "color2.g" => [@outline_color[1], :char],
513                          "color2.b" => [@outline_color[2], :char],
514                          "color2.a" => [@outline_color[3], :char],
515                          "color3.r" => [@shadow_color[0], :char],
516                          "color3.g" => [@shadow_color[1], :char],
517                          "color3.b" => [@shadow_color[2], :char],
518                          "color3.a" => [@shadow_color[3], :char],
519                          "text.text" => [@text],
520                          "text.text_class" => [@text_class],
521                          "text.font" => [@font],
522                          "text.size" => [@font_size],
523                          "text.fit_x" => [@fit[0]],
524                          "text.fit_y" => [@fit[1]],
525                          "text.min_x" => [@text_min[0]],
526                          "text.min_y" => [@text_min[1]],
527                          "text.align.x" => [@text_align[0], :double],
528                          "text.align.y" => [@text_align[1], :double],
529                          "text.id_source" => [@text_id_source],
530                          "text.id_text_source" => [@text_id_text_source]})
531                 end
532         end
533 end