Several updates - added support for second pattern engine and adding commands
[led-pattern-ed] / src / led-pattern-view.vala
1 /* This file is part of LED Pattern Editor.
2  *
3  * Copyright (C) 2010 Philipp Zabel
4  *
5  * LED Pattern Editor is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * LED Pattern Editor is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with LED Pattern Editor. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 class LedPatternView : Gtk.DrawingArea {
20         public LedPatternRX51 pattern;
21         public double duration;
22
23         public LedPatternView (LedPatternRX51? _pattern = null) {
24                 pattern = _pattern;
25                 if (pattern != null)
26                         pattern.changed.connect (update);
27                 update_duration ();
28         }
29
30         public void update_duration () {
31                 duration = 1.0;
32                 if (pattern != null) {
33                         while (pattern.duration > 1000 * duration) {
34                                 duration += 1.0;
35                         }
36                 }
37         }
38
39         public override bool expose_event (Gdk.EventExpose event) {
40
41                 update_duration ();
42
43                 var ctx = Gdk.cairo_create (window);
44                 int height = allocation.height;
45                 int width = allocation.width;
46                 double pps = width / duration; // pixel per second
47
48                 ctx.rectangle (event.area.x, event.area.y, event.area.width, event.area.height);
49                 ctx.clip ();
50
51                 ctx.set_source_rgb (0, 0, 0);
52                 ctx.set_line_width (1.0);
53
54                 ctx.set_line_join (Cairo.LineJoin.ROUND);
55
56                 ctx.new_path ();
57                 ctx.move_to (0, 0);
58                 ctx.line_to (width, 0);
59                 ctx.line_to (width, height);
60                 ctx.line_to (0, height);
61                 ctx.close_path ();
62                 ctx.fill ();
63
64                 ctx.set_source_rgb (0.33, 0.33, 0.33);
65                 ctx.new_path ();
66
67                 // 0%, 50%, 100%
68                 ctx.move_to (0.5, 0.5);
69                 ctx.line_to (width - 0.5, 0.5);
70
71                 ctx.move_to (0.5, height / 2 - 0.5);
72                 ctx.line_to (width - 0.5, height / 2 - 0.5);
73
74                 ctx.move_to (0.5, height - 0.5);
75                 ctx.line_to (width - 0.5, height - 0.5);
76
77                 // 0s, 1s, 2s, 3s, 4s
78                 for (double time = 0; time <= duration; time += 1.0) {
79                         ctx.move_to (time * pps + 0.5, 0.5);
80                         ctx.line_to (time * pps + 0.5, 139.5);
81                 }
82                 ctx.stroke ();
83
84                 if (pattern != null) {
85                         if (pattern.color1 != LedColor.OFF)
86                                 draw_pattern (ctx, width, height,
87                                               pattern.color1, pattern.engine1);
88                         if (pattern.color2 != LedColor.OFF)
89                                 draw_pattern (ctx, width, height,
90                                               pattern.color2, pattern.engine2);
91                 }
92                 return true;
93         }
94
95         private void draw_pattern (Cairo.Context ctx, int width, int height, LedColor color,
96                                    List<LedCommandRX51> engine) {
97                 double pps = width / duration; // pixel per second
98
99                 ctx.new_path ();
100
101                 ctx.set_operator (Cairo.Operator.ADD);
102
103                 ctx.set_source_rgb ((LedColor.R in color) ? 1.0 : 0.0,
104                                     (LedColor.G in color) ? 1.0 : 0.0,
105                                     (LedColor.B in color) ? 1.0 : 0.0);
106                 ctx.set_line_width (3.0);
107
108                 double x = 0, y = 0;
109                 foreach (LedCommand command in engine) {
110                         x = command.time * pps/1000.0;
111                         y = (255 - command.level) * (height - 1)/255.0;
112                         switch (command.type) {
113                         case CommandType.RAMP_WAIT:
114                         case CommandType.TRIGGER:
115                                 x += command.duration * pps/1000.0;
116                                 y -= command.steps * (height - 1)/255.0;
117                                 if (y < 0)
118                                         y = 0;
119                                 if (y > (height - 1))
120                                         y = height - 1;
121                                 ctx.line_to (x, y);
122                                 break;
123                         default:
124                                 ctx.line_to (x, y);
125                                 break;
126                         }
127                 }
128                 ctx.stroke ();
129
130                 ctx.set_source_rgb ((LedColor.R in color) ? 0.75 : 0.0,
131                                     (LedColor.G in color) ? 0.75 : 0.0,
132                                     (LedColor.B in color) ? 0.75 : 0.0);
133                 ctx.set_line_width (1.0);
134
135                 LedCommandRX51 last_command = null;
136                 foreach (LedCommandRX51 command in engine) {
137                         if (command.type == CommandType.END ||
138                             command.type == CommandType.GO_TO_START) {
139                                 last_command = command;
140                                 break;
141                         }
142                 }
143                 if (last_command == null)
144                         return;
145
146                 if (last_command.type == CommandType.END) {
147                         ctx.new_path ();
148
149                         ctx.move_to (x, y);
150                         if (last_command.steps == -255) {
151                                 ctx.line_to (x, height - 0.5);
152                                 ctx.line_to (width - 0.5, height - 0.5);
153                         } else {
154                                 ctx.line_to (width - 0.5, y);
155                         }
156
157                         ctx.stroke ();
158                 }
159                 var engine_duration = last_command.time + last_command.duration;
160                 if (last_command.type == CommandType.GO_TO_START && engine_duration >= 3 * 0.49) {
161                         ctx.new_path ();
162
163                         for (double offset = engine_duration; (offset * pps/1000.0) <= width; offset += engine_duration) {
164                                 ctx.move_to (x, y);
165                                 foreach (LedCommand command in engine) {
166                                         x = (command.time + offset) * pps/1000.0;
167                                         y = (255 - command.level) * (height - 1)/255.0;
168                                         if (x >= width)
169                                                 break;
170                                         switch (command.type) {
171                                         case CommandType.RAMP_WAIT:
172                                         case CommandType.TRIGGER:
173                                                 x += command.duration * pps/1000.0;
174                                                 y -= command.steps * (height - 1)/255.0;
175                                                 if (y < 0)
176                                                         y = 0;
177                                                 if (y > (height - 1))
178                                                         y = height - 1;
179                                                 ctx.line_to (x, y);
180                                                 break;
181                                         default:
182                                                 ctx.line_to (x, y);
183                                                 break;
184                                         }
185                                 }
186                         }
187
188                         ctx.stroke ();
189                 }
190                 if (last_command.type == CommandType.GO_TO_START && engine_duration < 3 * 0.49) {
191                         ctx.new_path ();
192
193
194                         ctx.move_to (x, y);
195                         ctx.line_to (width - 0.5, y);
196
197                         ctx.stroke ();
198                 }
199         }
200
201         public void update () {
202                 unowned Gdk.Region region = window.get_clip_region ();
203
204                 // redraw the cairo canvas completely by exposing it
205                 window.invalidate_region (region, true);
206                 window.process_updates (true);
207         }
208 }