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 +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.
|