scratchpad-app 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.