scratchpad-app 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2884ac594e63c4fd66c60441cb52c595695647cf
4
- data.tar.gz: 51c7ea9f35da8ae5bc63c889fdb774164700220e
3
+ metadata.gz: 8cd78a3303c728a889426ab83b482fdd916e9d45
4
+ data.tar.gz: 8cda57dffa9ac5231ad91e5b544e39342439422e
5
5
  SHA512:
6
- metadata.gz: abada001f419834283f7cff672a162661333f5874717536ea9a74efdc9aad09ec49874e0d459da25603739658452367bbf0e60a80faddce8b95ac61a10b0d420
7
- data.tar.gz: 3ad90cab79e78509edfb3d7cf61ab76d88a40aefda89b0f869ae9d4e8f23c69d2f3776c23ae7374408d6ddec27ecd39fb0e8dac1ea70a097e5a6137eeec319b3
6
+ metadata.gz: d4f1081f5fa861c46dffb23cba4e78af918b90082ad861365af2cdf1cac15c2414760d71e54686c5afe4949256350fa4c65a2a0160b051138878af2d7d98a972
7
+ data.tar.gz: a028e60e3382d2eb7c16e7c9ce8a47183bbcea880ad7acff0d858492852eaf554c4befaaf82596ef390947bac0e3fae4ad9cd31934b49d632839cd69e697a583
data/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  ## 使い方
11
11
 
12
- 左マウスボタンで線が書けて、右クリックで全消去します。
12
+ 左マウスボタンで線が書けて、右クリックでメニューが出ます。
13
13
 
14
14
  画像の保存機能や消しゴム機能はありません。
15
15
 
@@ -4,6 +4,24 @@ require "scratchpad/version"
4
4
  require 'toml'
5
5
  require 'gtk2'
6
6
 
7
+ class Array
8
+ def x
9
+ self[0]
10
+ end
11
+
12
+ def x=(val)
13
+ self[0]=val
14
+ end
15
+
16
+ def y
17
+ self[1]
18
+ end
19
+
20
+ def y=(val)
21
+ self[1]=val
22
+ end
23
+ end
24
+
7
25
  module Scratchpad
8
26
 
9
27
  # 便利関数
@@ -35,6 +53,26 @@ def color_bytes_to_floats(bytes)
35
53
  return bytes.map { |b| b / 255.0 }
36
54
  end
37
55
 
56
+ def plus(v1, v2)
57
+ [v1.x + v2.x, v1.y + v2.y]
58
+ end
59
+
60
+ def vector(p1, p2)
61
+ [p2.x - p1.x, p2.y - p1.y]
62
+ end
63
+
64
+ def dot_product(v1, v2)
65
+ return v1.x * v2.x + v1.y * v2.y
66
+ end
67
+
68
+ def scalar_times(factor, v)
69
+ v.map { |x| factor * x }
70
+ end
71
+
72
+ def vec_lerp(alpha, v1, v2)
73
+ return plus(scalar_times(alpha, v1), scalar_times(1.0-alpha, v2))
74
+ end
75
+
38
76
  # /便利関数
39
77
 
40
78
  class Pen
@@ -42,14 +80,17 @@ class Pen
42
80
  extend Scratchpad
43
81
 
44
82
  attr_reader :x, :y
83
+ attr_accessor :color
45
84
 
46
- FAVORITE_ORANGE = color_bytes_to_floats [255, 109, 50]
47
- FAVORITE_BLUE = color_bytes_to_floats [0, 3, 126]
85
+ BLUE = color_bytes_to_floats [0, 3, 126]
86
+ ORANGE = color_bytes_to_floats [255, 109, 50]
87
+ GREEN = color_bytes_to_floats [0, 124, 126]
48
88
 
49
89
  def initialize
50
90
  @is_down = false
51
91
  @interpolator = Interpolator.new
52
92
  @path = []
93
+ @color = BLUE
53
94
  end
54
95
 
55
96
  # [[x, y, radius]*]
@@ -82,10 +123,6 @@ class Pen
82
123
  def radius
83
124
  2.0
84
125
  end
85
-
86
- def color
87
- FAVORITE_BLUE
88
- end
89
126
  end
90
127
 
91
128
  class SheetModel < GLib::Object
@@ -95,12 +132,14 @@ class SheetModel < GLib::Object
95
132
  include Math
96
133
  include Cairo
97
134
 
98
- attr_reader :surface, :pen, :debug_surface
135
+ attr_reader :surface, :pen, :debug_surface, :outline_surface
99
136
 
100
- def initialize
137
+ def initialize(width, height)
101
138
  super()
102
- @surface = ImageSurface.new(Cairo::FORMAT_ARGB32, 1200, 900)
103
- @debug_surface = ImageSurface.new(Cairo::FORMAT_ARGB32, 1200, 900)
139
+ @surface = ImageSurface.new(Cairo::FORMAT_ARGB32, width, height)
140
+ @debug_surface = ImageSurface.new(Cairo::FORMAT_ARGB32, width, height)
141
+ @outline_surface = ImageSurface.new(Cairo::FORMAT_ARGB32, width, height)
142
+
104
143
  cr = Context.new(@surface)
105
144
  cr.set_operator(Cairo::OPERATOR_OVER)
106
145
  cr.line_cap = cr.line_join = :round
@@ -116,17 +155,14 @@ class SheetModel < GLib::Object
116
155
  end
117
156
 
118
157
  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
158
+ clear_surface(@surface)
159
+ clear_surface(@outline_surface)
160
+ clear_surface(@debug_surface)
125
161
  signal_emit('changed')
126
162
  end
127
163
 
128
- def clear_debug
129
- c = Context.new(@debug_surface)
164
+ def clear_surface(surface)
165
+ c = Context.new(surface)
130
166
  c.save do
131
167
  c.set_operator(Cairo::OPERATOR_SOURCE)
132
168
  c.set_source_rgba(0, 0, 0, 0)
@@ -155,55 +191,61 @@ class SheetModel < GLib::Object
155
191
  case @portion
156
192
  when :all
157
193
  path = @pen.path
194
+
195
+ stroke_outline(path)
196
+
158
197
  cr.set_source_rgba(@pen.color)
159
198
  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
199
+ stroke_path(cr, path)
168
200
  when :latter_half
169
201
  path = @pen.path[5..10] || []
202
+
203
+ stroke_outline(path)
204
+
170
205
  cr.set_source_rgba(@pen.color)
171
206
  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
207
+ stroke_path(cr, path)
181
208
  end
182
209
  @portion = :all
210
+ signal_emit('changed')
183
211
  else
184
212
  if @portion == :first_half
185
213
  path = @pen.path[0..4] || []
214
+
215
+ stroke_outline(path)
216
+
186
217
  cr.set_source_rgba(@pen.color)
187
218
  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
219
+ stroke_path(cr, path)
220
+
196
221
  @portion = :all
222
+ signal_emit('changed')
197
223
  end
198
224
  end
199
- signal_emit('changed')
225
+ end
226
+
227
+ def stroke_outline(path)
228
+ cr = Cairo::Context.new(@outline_surface)
229
+ cr.line_cap = cr.line_join = :round
230
+ cr.set_source_rgba([1, 1, 1, 1.0])
231
+ cr.line_width = @pen.radius * 3
232
+ stroke_path(cr, path)
233
+ end
234
+
235
+ def stroke_path(cr, path)
236
+ path.each.with_index do |(x, y, radius), i|
237
+ if i == 0
238
+ cr.move_to(x, y)
239
+ else
240
+ cr.line_to(x, y)
241
+ end
242
+ end
243
+ cr.stroke
200
244
  end
201
245
 
202
246
  def pen_move(ev)
203
247
  @pen.move(ev)
204
248
  update
205
-
206
- signal_emit('changed')
207
249
  end
208
250
  end
209
251
 
@@ -214,69 +256,68 @@ class SheetView < Gtk::DrawingArea
214
256
  super()
215
257
  self.app_paintable = true
216
258
 
259
+ @dirty = true
260
+
217
261
  set_size_request(800, 600)
218
262
  signal_connect('expose-event') do
219
263
  draw
220
264
  true
221
265
  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
266
 
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
267
+ # モーションノティファイ。
268
+ @last_point = nil
269
+ signal_connect('motion-notify-event') do |self_, ev|
270
+ handle_motion_notify_event(ev)
247
271
  end
272
+
273
+ # ボタンプレス。このイベントは同じ座標へのモーションノティファイの
274
+ # 後に来る。
248
275
  signal_connect('button-press-event') do |self_, ev|
276
+ # デバッグ描画。ボタン押下座標に赤の四角を描く。
249
277
  c = Cairo::Context.new(@model.debug_surface)
250
278
  c.set_source_rgba([1, 0, 0])
251
279
  c.rectangle(ev.x - 2, ev.y - 2, 4, 4)
252
280
  c.fill
253
281
  c.destroy
254
282
 
255
-
256
283
  if ev.button == 1
257
284
  @model.pen_down(ev)
285
+ elsif ev.button == 2
286
+ # 押し間違いを防ぐ。
287
+ unless @model.pen.down?
288
+ @model.clear
289
+ end
258
290
  elsif ev.button == 3
259
- @model.clear
291
+ # 押し間違いを防ぐ。
292
+ unless @model.pen.down?
293
+ menu_popup(ev)
294
+ end
260
295
  end
261
296
  end
297
+
298
+ # ボタンリリース。このイベントは同じ座標へのモーションノティファイ
299
+ # の後に来る。
262
300
  signal_connect('button-release-event') do |self_, ev|
301
+ # デバッグ描画。ボタンリリース座標に赤の四角を描く。
263
302
  c = Cairo::Context.new(@model.debug_surface)
264
303
  c.set_source_rgba([1, 0, 0])
265
304
  c.rectangle(ev.x - 2, ev.y - 2, 4, 4)
266
305
  c.fill
267
306
  c.destroy
268
307
 
269
- if ev.button == 1
308
+ if ev.button == 1 && @model.pen.down?
270
309
  @model.pen_move(ev)
271
310
  @model.pen_up(ev)
272
311
  end
273
312
  end
274
313
 
275
- @model = SheetModel.new
314
+ @model = SheetModel.new(screen.width, screen.height)
276
315
  @model.signal_connect('changed') do
277
- # invalidate
316
+ @dirty = true
278
317
  end
279
318
 
319
+ # POINTER_MOTION_HINT_MASKを付けるとこちらの反応が遅い場合にポイン
320
+ # ター座標を省略する。
280
321
  self.events |= Gdk::Event::BUTTON_PRESS_MASK |
281
322
  Gdk::Event::BUTTON_RELEASE_MASK |
282
323
  # Gdk::Event::POINTER_MOTION_HINT_MASK |
@@ -284,6 +325,88 @@ class SheetView < Gtk::DrawingArea
284
325
 
285
326
  end
286
327
 
328
+ def tick
329
+ return if @last_motion_notify_event.nil?
330
+
331
+ t = Time.now
332
+ if (t - @last_motion_notify_event_time) > 0.033
333
+ handle_motion_notify_event(@last_motion_notify_event)
334
+ @last_motion_notify_event_time = t
335
+ end
336
+ end
337
+
338
+ def handle_motion_notify_event(ev)
339
+ @last_motion_notify_event = ev.dup
340
+ @last_motion_notify_event_time = Time.now
341
+
342
+ ev.x = ev.x + 0.5
343
+ ev.y = ev.y + 0.5
344
+
345
+ # デバッグ描画。ポインター座標に緑の四角を描く。
346
+ c = Cairo::Context.new(@model.debug_surface)
347
+ c.set_source_rgba([0, 1, 0])
348
+ c.rectangle(ev.x - 1, ev.y - 1, 2, 2)
349
+ c.fill
350
+ c.destroy
351
+
352
+ if @last_point == nil
353
+ @last_point = [ev.x, ev.y]
354
+ @model.pen_move(ev)
355
+ else
356
+ # 1サンプル時間あたり距離30以上移動した場合は完全にポインター
357
+ # 座標にならう。
358
+ d = distance(@last_point, [ev.x, ev.y])
359
+ alpha = if d <= 5.0 then 0.30 else 0.60 end
360
+ p [d, alpha] if $DEBUG
361
+ @last_point = vec_lerp(alpha, [ev.x, ev.y], @last_point)
362
+ ev.x, ev.y = @last_point
363
+ @model.pen_move(ev)
364
+ end
365
+ end
366
+
367
+ def dirty?
368
+ @dirty
369
+ end
370
+
371
+ def create_context_menu
372
+ menu = Gtk::Menu.new
373
+
374
+ item1 = Gtk::MenuItem.new('クリア')
375
+ item1.signal_connect('activate') do
376
+ @model.clear
377
+ end
378
+ menu.append(item1)
379
+
380
+ menu.append(Gtk::MenuItem.new)
381
+
382
+ item2 = Gtk::MenuItem.new('青')
383
+ item2.signal_connect('activate') do
384
+ @model.pen.color = Pen::BLUE
385
+ end
386
+ menu.append(item2)
387
+
388
+ item3 = Gtk::MenuItem.new('オレンジ')
389
+ item3.signal_connect('activate') do
390
+ @model.pen.color = Pen::ORANGE
391
+ end
392
+ menu.append(item3)
393
+
394
+ item4 = Gtk::MenuItem.new('緑')
395
+ item4.signal_connect('activate') do
396
+ @model.pen.color = Pen::GREEN
397
+ end
398
+ menu.append(item4)
399
+
400
+ menu.show_all
401
+
402
+ return menu
403
+ end
404
+
405
+ def menu_popup(button_event)
406
+ menu = create_context_menu
407
+ menu.popup(nil, nil, button_event.button, button_event.time)
408
+ end
409
+
287
410
  def draw
288
411
  cr = window.create_cairo_context
289
412
 
@@ -292,10 +415,17 @@ class SheetView < Gtk::DrawingArea
292
415
  cr.paint
293
416
 
294
417
  cr.set_operator(Cairo::OPERATOR_OVER)
418
+
419
+ cr.set_source(@model.outline_surface)
420
+ cr.paint
421
+
295
422
  cr.set_source(@model.surface)
296
423
  cr.paint
297
- # cr.set_source(@model.debug_surface)
298
- # cr.paint
424
+
425
+ if $DEBUG
426
+ cr.set_source(@model.debug_surface)
427
+ cr.paint
428
+ end
299
429
 
300
430
  cr.destroy
301
431
  end
@@ -303,6 +433,7 @@ class SheetView < Gtk::DrawingArea
303
433
  def invalidate
304
434
  window.invalidate(window.clip_region, true)
305
435
  window.process_updates(true)
436
+ @dirty = false
306
437
  end
307
438
  end
308
439
 
@@ -327,23 +458,20 @@ class Program
327
458
  include Gtk
328
459
 
329
460
  def initialize
330
- @quit_requested = false
331
-
332
461
  Signal.trap(:INT) {
333
462
  STDERR.puts("Interrupted")
334
- Gtk.main_quit
335
- @quit_requested = true
463
+ quit
336
464
  }
337
465
  end
338
466
 
339
467
  def quit
340
- @quit_requested = true
468
+ Gtk.main_quit
341
469
  end
342
470
 
343
471
  def run
344
472
  win = MainWindow.new
345
473
  win.signal_connect('delete-event') do
346
- Gtk.main_quit
474
+ quit
347
475
  end
348
476
 
349
477
  win.maximize
@@ -352,12 +480,13 @@ class Program
352
480
  win.show_all
353
481
 
354
482
  Gtk.timeout_add(33) do
355
- sheet.invalidate
483
+ sheet.tick
484
+ if sheet.dirty?
485
+ sheet.invalidate
486
+ end
487
+ true
356
488
  end
357
489
 
358
- # until @quit_requested
359
- # Gtk.main_iteration while Gtk.events_pending?
360
- # end
361
490
  Gtk.main
362
491
  end
363
492
  end
@@ -1,3 +1,3 @@
1
1
  module Scratchpad
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scratchpad-app
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yoteichi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-07 00:00:00.000000000 Z
11
+ date: 2017-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: toml
@@ -121,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
121
  version: '0'
122
122
  requirements: []
123
123
  rubyforge_project:
124
- rubygems_version: 2.5.1
124
+ rubygems_version: 2.6.8
125
125
  signing_key:
126
126
  specification_version: 4
127
127
  summary: A paint program for note taking.