scratchpad-app 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ # Scratchpad
2
+
3
+ メモ書き用の簡易ペイントソフトです。半透明の背景で最大化されて起動しま
4
+ す。
5
+
6
+ ## インストール
7
+
8
+ $ gem install scratchpad-app
9
+
10
+ ## 使い方
11
+
12
+ 左マウスボタンで線が書けて、右クリックで全消去します。
13
+
14
+ 画像の保存機能や消しゴム機能はありません。
15
+
16
+ ## 貢献
17
+
18
+ バグ報告などは https://github.com/plonk/scratchpad まで。
19
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "scratchpad"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'scratchpad'
4
+
5
+ Scratchpad::Program.new.run
@@ -0,0 +1,365 @@
1
+ require "scratchpad/interpolator"
2
+ require "scratchpad/version"
3
+
4
+ require 'toml'
5
+ require 'gtk2'
6
+
7
+ module Scratchpad
8
+
9
+ # 便利関数
10
+ def distance(p1, p2)
11
+ x1, y1 = p1
12
+ x2, y2 = p2
13
+
14
+ return Math.sqrt((x1 - x2)**2 + (y1 - y2)**2)
15
+ end
16
+
17
+ def chess_distance(p1, p2)
18
+ x1, y1 = p1
19
+ x2, y2 = p2
20
+
21
+ return [(x1 - x2).abs, (y1 - y2).abs].max
22
+ end
23
+
24
+ def midpoint(p1, p2)
25
+ x1, y1 = p1
26
+ x2, y2 = p2
27
+
28
+ return [(x1 + x2) * 0.5, (y1 + y2) * 0.5]
29
+ end
30
+
31
+ def color_bytes_to_floats(bytes)
32
+ raise 'wrong range' unless bytes.all? { |x| x >= 0 && x <= 255 }
33
+ raise 'wrong dimension' unless bytes.size.between?(3, 4)
34
+
35
+ return bytes.map { |b| b / 255.0 }
36
+ end
37
+
38
+ # /便利関数
39
+
40
+ class Pen
41
+ include Math
42
+ extend Scratchpad
43
+
44
+ attr_reader :x, :y
45
+
46
+ FAVORITE_ORANGE = color_bytes_to_floats [255, 109, 50]
47
+ FAVORITE_BLUE = color_bytes_to_floats [0, 3, 126]
48
+
49
+ def initialize
50
+ @is_down = false
51
+ @interpolator = Interpolator.new
52
+ @path = []
53
+ end
54
+
55
+ # [[x, y, radius]*]
56
+ def path
57
+ f = proc { |x|
58
+ [-5.0/10.0 * x + 8.0, 0.3].max
59
+ #x
60
+ }
61
+ @path.map { |x, y, velocity|
62
+ [x, y, sqrt(f.(velocity))]
63
+ }
64
+ end
65
+
66
+ def down(ev)
67
+ @is_down = true
68
+ end
69
+
70
+ def up(ev)
71
+ @is_down = false
72
+ end
73
+
74
+ def move(ev)
75
+ @path = @interpolator.feed(ev.x, ev.y, @is_down)
76
+ end
77
+
78
+ def down?
79
+ @is_down
80
+ end
81
+
82
+ def radius
83
+ 2.0
84
+ end
85
+
86
+ def color
87
+ FAVORITE_BLUE
88
+ end
89
+ end
90
+
91
+ class SheetModel < GLib::Object
92
+ type_register
93
+ signal_new('changed', GLib::Signal::ACTION, nil, nil)
94
+
95
+ include Math
96
+ include Cairo
97
+
98
+ attr_reader :surface, :pen, :debug_surface
99
+
100
+ def initialize
101
+ super()
102
+ @surface = ImageSurface.new(Cairo::FORMAT_ARGB32, 1200, 900)
103
+ @debug_surface = ImageSurface.new(Cairo::FORMAT_ARGB32, 1200, 900)
104
+ cr = Context.new(@surface)
105
+ cr.set_operator(Cairo::OPERATOR_OVER)
106
+ cr.line_cap = cr.line_join = :round
107
+ @context = cr
108
+
109
+ @pen = Pen.new
110
+
111
+ @portion = :all
112
+ end
113
+
114
+ def cr
115
+ @context
116
+ end
117
+
118
+ def clear
119
+ cr.save do
120
+ cr.set_operator(Cairo::OPERATOR_SOURCE)
121
+ cr.set_source_rgba(0, 0, 0, 0)
122
+ cr.paint
123
+ end
124
+ clear_debug
125
+ signal_emit('changed')
126
+ end
127
+
128
+ def clear_debug
129
+ c = Context.new(@debug_surface)
130
+ c.save do
131
+ c.set_operator(Cairo::OPERATOR_SOURCE)
132
+ c.set_source_rgba(0, 0, 0, 0)
133
+ c.paint
134
+ end
135
+ c.destroy
136
+ end
137
+
138
+ def pen_down(ev)
139
+ @pen.down(ev)
140
+ @portion = :latter_half
141
+ end
142
+
143
+ def pen_up(ev)
144
+ @pen.up(ev)
145
+ @portion = :first_half
146
+ end
147
+
148
+ def normalize(ary)
149
+ sum = ary.max
150
+ ary.map { |x| x / sum }
151
+ end
152
+
153
+ def update
154
+ if @pen.down?
155
+ case @portion
156
+ when :all
157
+ path = @pen.path
158
+ cr.set_source_rgba(@pen.color)
159
+ cr.line_width = @pen.radius
160
+ path.each.with_index do |(x, y, radius), i|
161
+ if i == 0
162
+ cr.move_to(x, y)
163
+ else
164
+ cr.line_to(x, y)
165
+ end
166
+ end
167
+ cr.stroke
168
+ when :latter_half
169
+ path = @pen.path[5..10] || []
170
+ cr.set_source_rgba(@pen.color)
171
+ cr.line_width = @pen.radius
172
+ path.each.with_index do |(x, y, radius), i|
173
+ if i == 0
174
+ cr.move_to(x, y)
175
+ else
176
+ # cr.arc(x, y, (i/4.0)*@pen.radius, 0, 2*PI)
177
+ cr.line_to(x, y)
178
+ end
179
+ end
180
+ cr.stroke
181
+ end
182
+ @portion = :all
183
+ else
184
+ if @portion == :first_half
185
+ path = @pen.path[0..4] || []
186
+ cr.set_source_rgba(@pen.color)
187
+ cr.line_width = @pen.radius
188
+ path.each.with_index do |(x, y, radius), i|
189
+ if i == 0
190
+ cr.move_to(x, y)
191
+ else
192
+ cr.line_to(x, y)
193
+ end
194
+ end
195
+ cr.stroke
196
+ @portion = :all
197
+ end
198
+ end
199
+ signal_emit('changed')
200
+ end
201
+
202
+ def pen_move(ev)
203
+ @pen.move(ev)
204
+ update
205
+
206
+ signal_emit('changed')
207
+ end
208
+ end
209
+
210
+ class SheetView < Gtk::DrawingArea
211
+ include Scratchpad
212
+
213
+ def initialize()
214
+ super()
215
+ self.app_paintable = true
216
+
217
+ set_size_request(800, 600)
218
+ signal_connect('expose-event') do
219
+ draw
220
+ true
221
+ end
222
+ last_point = nil
223
+ signal_connect('motion-notify-event') do |self_, ev|
224
+ ev.x = ev.x + 0.5
225
+ ev.y = ev.y + 0.5
226
+
227
+ c = Cairo::Context.new(@model.debug_surface)
228
+ c.set_source_rgba([0, 1, 0])
229
+ c.rectangle(ev.x - 1, ev.y - 1, 2, 2)
230
+ # c.set_source_rgba([0, 0, 1])
231
+ # c.rectangle(ev.x - 2, ev.y - 2, 4, 4)
232
+ c.fill
233
+ c.destroy
234
+
235
+ if last_point == nil
236
+ last_point = [ev.x, ev.y]
237
+ @model.pen_move(ev)
238
+ else
239
+ if distance(last_point, [ev.x, ev.y]) < (1.0/0.0)
240
+ last_point = midpoint(last_point, [ev.x, ev.y])
241
+ ev.x, ev.y = last_point
242
+ else
243
+ last_point = [ev.x, ev.y]
244
+ end
245
+ @model.pen_move(ev)
246
+ end
247
+ end
248
+ signal_connect('button-press-event') do |self_, ev|
249
+ c = Cairo::Context.new(@model.debug_surface)
250
+ c.set_source_rgba([1, 0, 0])
251
+ c.rectangle(ev.x - 2, ev.y - 2, 4, 4)
252
+ c.fill
253
+ c.destroy
254
+
255
+
256
+ if ev.button == 1
257
+ @model.pen_down(ev)
258
+ elsif ev.button == 3
259
+ @model.clear
260
+ end
261
+ end
262
+ signal_connect('button-release-event') do |self_, ev|
263
+ c = Cairo::Context.new(@model.debug_surface)
264
+ c.set_source_rgba([1, 0, 0])
265
+ c.rectangle(ev.x - 2, ev.y - 2, 4, 4)
266
+ c.fill
267
+ c.destroy
268
+
269
+ if ev.button == 1
270
+ @model.pen_move(ev)
271
+ @model.pen_up(ev)
272
+ end
273
+ end
274
+
275
+ @model = SheetModel.new
276
+ @model.signal_connect('changed') do
277
+ # invalidate
278
+ end
279
+
280
+ self.events |= Gdk::Event::BUTTON_PRESS_MASK |
281
+ Gdk::Event::BUTTON_RELEASE_MASK |
282
+ # Gdk::Event::POINTER_MOTION_HINT_MASK |
283
+ Gdk::Event::POINTER_MOTION_MASK
284
+
285
+ end
286
+
287
+ def draw
288
+ cr = window.create_cairo_context
289
+
290
+ cr.set_operator(Cairo::OPERATOR_SOURCE)
291
+ cr.set_source_rgba(1.0, 1.0, 1.0, 0.5)
292
+ cr.paint
293
+
294
+ cr.set_operator(Cairo::OPERATOR_OVER)
295
+ cr.set_source(@model.surface)
296
+ cr.paint
297
+ # cr.set_source(@model.debug_surface)
298
+ # cr.paint
299
+
300
+ cr.destroy
301
+ end
302
+
303
+ def invalidate
304
+ window.invalidate(window.clip_region, true)
305
+ window.process_updates(true)
306
+ end
307
+ end
308
+
309
+ class MainWindow < Gtk::Window
310
+ def initialize
311
+ super()
312
+
313
+ self.title = "Scratchpad"
314
+
315
+ set_rgba_colormap
316
+ signal_connect('screen-changed') do
317
+ set_rgba_colormap
318
+ end
319
+ end
320
+
321
+ def set_rgba_colormap
322
+ self.colormap = screen.rgba_colormap || screen.rgb_colormap
323
+ end
324
+ end
325
+
326
+ class Program
327
+ include Gtk
328
+
329
+ def initialize
330
+ @quit_requested = false
331
+
332
+ Signal.trap(:INT) {
333
+ STDERR.puts("Interrupted")
334
+ Gtk.main_quit
335
+ @quit_requested = true
336
+ }
337
+ end
338
+
339
+ def quit
340
+ @quit_requested = true
341
+ end
342
+
343
+ def run
344
+ win = MainWindow.new
345
+ win.signal_connect('delete-event') do
346
+ Gtk.main_quit
347
+ end
348
+
349
+ win.maximize
350
+ sheet = SheetView.new
351
+ win.add sheet
352
+ win.show_all
353
+
354
+ Gtk.timeout_add(33) do
355
+ sheet.invalidate
356
+ end
357
+
358
+ # until @quit_requested
359
+ # Gtk.main_iteration while Gtk.events_pending?
360
+ # end
361
+ Gtk.main
362
+ end
363
+ end
364
+
365
+ end
@@ -0,0 +1,132 @@
1
+ module Scratchpad
2
+
3
+ class Interpolator
4
+ include Math
5
+
6
+ class DataPoint < Struct.new(:v, :pos)
7
+ end
8
+
9
+ def initialize
10
+ @x_v = nil
11
+ @y_v = nil
12
+ @x = nil
13
+ @y = nil
14
+ @ring = [nil, nil, nil]
15
+ end
16
+
17
+ X = 0
18
+ Y = 1
19
+
20
+ # (x, y) -> [[Float, Float, Float]]
21
+ def feed(x, y, is_down)
22
+ raise unless @ring.size == 3
23
+
24
+ @ring.shift
25
+ @ring << [DataPoint.new(nil, x), DataPoint.new(nil, y), is_down]
26
+
27
+ if @ring[1] == nil
28
+ # cannot calculate yet
29
+ return []
30
+ else
31
+ # calculate @ring[1]'s velocity
32
+ if @ring[0] == nil
33
+ @ring[1][X].v = (@ring[2][X].pos - @ring[1][X].pos)
34
+ @ring[1][Y].v = (@ring[2][Y].pos - @ring[1][Y].pos)
35
+ return []
36
+ else
37
+ @ring[1][X].v = ((@ring[1][X].pos - @ring[0][X].pos) + (@ring[2][X].pos - @ring[1][X].pos)) * 0.5
38
+ @ring[1][Y].v = ((@ring[1][Y].pos - @ring[0][Y].pos) + (@ring[2][Y].pos - @ring[1][Y].pos)) * 0.5
39
+ # unless @ring[1][2] # is_down
40
+ # return []
41
+ # else
42
+ return interpolate
43
+ # end
44
+ end
45
+ end
46
+ end
47
+
48
+ # interpolate using @ring[0] and @ring[1]
49
+ def interpolate
50
+ a, b = find_accelerations(@ring[0][X].pos,
51
+ @ring[0][X].v,
52
+ @ring[1][X].pos,
53
+ @ring[1][X].v)
54
+ s = @ring[0][X].pos
55
+ velocity = @ring[0][X].v
56
+ xpoints = (0..10).map { |n| n*0.1 }.map do |t|
57
+ [s, velocity].tap do
58
+ s += 0.1 * velocity
59
+ if t < 0.5
60
+ velocity += a * 0.1
61
+ else
62
+ velocity += b * 0.1
63
+ end
64
+ end
65
+ end
66
+
67
+ a, b = find_accelerations(@ring[0][Y].pos,
68
+ @ring[0][Y].v,
69
+ @ring[1][Y].pos,
70
+ @ring[1][Y].v)
71
+ s = @ring[0][Y].pos
72
+ velocity = @ring[0][Y].v
73
+ ypoints = (0..10).map { |n| n*0.1 }.map do |t|
74
+ [s, velocity].tap do
75
+ s += 0.1 * velocity
76
+ if t < 0.5
77
+ velocity += a * 0.1
78
+ else
79
+ velocity += b * 0.1
80
+ end
81
+ end
82
+ end
83
+
84
+ return xpoints.zip(ypoints).map { |x_data, y_data|
85
+ [x_data[0], y_data[0], sqrt(x_data[1] ** 2 + y_data[1] ** 2)]
86
+ }
87
+ end
88
+
89
+ def find_accelerations(s, velocity, target_position, target_velocity)
90
+ a = 0.0
91
+ b = 0.0
92
+
93
+ s_ = s
94
+ velocity_ = velocity
95
+
96
+ loop do
97
+ s = s_
98
+ velocity = velocity_
99
+
100
+ (0..9).map { |n| n*0.1 }.map do |t|
101
+ [t, s].tap do
102
+ s += 0.1 * velocity
103
+ if t < 0.5
104
+ velocity += a * 0.1
105
+ else
106
+ velocity += b * 0.1
107
+ end
108
+ end
109
+ end
110
+
111
+ if (s - target_position).abs >= 0.001
112
+ a += -(s - target_position) * 1.0
113
+ elsif (velocity - target_velocity).abs >= 0.001
114
+ b += -(velocity - target_velocity) * 1.0
115
+ else
116
+ return a, b
117
+ end
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ if __FILE__ == $0
124
+ obj = Interpolator.new
125
+ p obj.feed(0.0, 0.0)
126
+ p obj.feed(1.0, 1.0)
127
+ p obj.feed(2.0, 2.0)
128
+ p obj.feed(5.0, 5.0)
129
+ p obj.feed(0.0, 0.0)
130
+ end
131
+
132
+ end