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 +4 -4
- data/README.md +1 -1
- data/lib/scratchpad.rb +216 -87
- data/lib/scratchpad/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cd78a3303c728a889426ab83b482fdd916e9d45
|
4
|
+
data.tar.gz: 8cda57dffa9ac5231ad91e5b544e39342439422e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4f1081f5fa861c46dffb23cba4e78af918b90082ad861365af2cdf1cac15c2414760d71e54686c5afe4949256350fa4c65a2a0160b051138878af2d7d98a972
|
7
|
+
data.tar.gz: a028e60e3382d2eb7c16e7c9ce8a47183bbcea880ad7acff0d858492852eaf554c4befaaf82596ef390947bac0e3fae4ad9cd31934b49d632839cd69e697a583
|
data/README.md
CHANGED
data/lib/scratchpad.rb
CHANGED
@@ -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
|
-
|
47
|
-
|
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
|
103
|
-
@debug_surface
|
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
|
-
|
120
|
-
|
121
|
-
|
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
|
129
|
-
c = Context.new(
|
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
|
-
|
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
|
-
|
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
|
-
|
189
|
-
|
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
|
-
|
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
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
298
|
-
|
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
|
-
|
335
|
-
@quit_requested = true
|
463
|
+
quit
|
336
464
|
}
|
337
465
|
end
|
338
466
|
|
339
467
|
def quit
|
340
|
-
|
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
|
-
|
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.
|
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
|
data/lib/scratchpad/version.rb
CHANGED
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.
|
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:
|
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.
|
124
|
+
rubygems_version: 2.6.8
|
125
125
|
signing_key:
|
126
126
|
specification_version: 4
|
127
127
|
summary: A paint program for note taking.
|