Compare commits
26 Commits
eraserhead
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
32740f3b22 | |
|
|
6a2460c42f | |
|
|
721ad1eecf | |
|
|
0fdc73b9e4 | |
|
|
a2cf104697 | |
|
|
a1f813631b | |
|
|
f330760c1c | |
|
|
022377cb48 | |
|
|
3b048896e0 | |
|
|
390de72aa2 | |
|
|
57ef9e5e0e | |
|
|
56ed3b7c01 | |
|
|
827d8d6a1c | |
|
|
0b74394042 | |
|
|
3c6f161a54 | |
|
|
c72ac1cde3 | |
|
|
f42db5fb69 | |
|
|
7925cfb2d8 | |
|
|
7f5aae2714 | |
|
|
d456725e23 | |
|
|
2ddfef8416 | |
|
|
cfa925da2f | |
|
|
e6885125af | |
|
|
8f7e073c4c | |
|
|
c4ae8836e6 | |
|
|
1dac92b03b |
File diff suppressed because it is too large
Load Diff
32
Cargo.toml
32
Cargo.toml
|
|
@ -3,26 +3,30 @@ name = "stiletto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Enrico Lumetti <enrico.lumetti@gmail.com>"]
|
authors = ["Enrico Lumetti <enrico.lumetti@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
default-run = "stiletto"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
udev = "0.2"
|
log = "0.4"
|
||||||
libc = "0.2"
|
druid = { version = "0.7.0", features = ["im", "svg"] }
|
||||||
errno = "0.2"
|
|
||||||
csv = "1.1"
|
|
||||||
druid = { version = "0.6.0", features = ["im"] }
|
|
||||||
im = { version = "*" }
|
im = { version = "*" }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_bare = "0.3.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
clap = "3.0.0-beta.2"
|
||||||
|
|
||||||
[patch.crates-io]
|
[target.'cfg(target_os="linux")'.dependencies.gtk]
|
||||||
druid = { git = "https://github.com/doppioandante/druid", branch = "stylus_events_0.6.0", features = ["im"] }
|
version = "0.9.2"
|
||||||
|
|
||||||
[dependencies.gtk]
|
|
||||||
version = "0.8.1"
|
|
||||||
features = ["v3_22"]
|
features = ["v3_22"]
|
||||||
|
|
||||||
[dependencies.gio]
|
[target.'cfg(target_os="linux")'.dependencies.gio]
|
||||||
version = "0.8.1"
|
version = "0.9.1"
|
||||||
features = ["v2_56"]
|
features = ["v2_56"]
|
||||||
|
|
||||||
[dependencies.gdk]
|
[target.'cfg(target_os="linux")'.dependencies.gdk]
|
||||||
version = "0.12.1"
|
version = "0.13.2"
|
||||||
features = ["v3_22"]
|
features = ["v3_22"]
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
druid = { git = "https://github.com/doppioandante/druid", branch = "v0.7.0_stiletto", features = ["im", "svg"] }
|
||||||
|
#druid = { path = "../druid/druid/", features = ["im", "svg"] }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 103.38" style="enable-background:new 0 0 122.88 103.38" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#f0f0f0;}</style><g><path class="st0" stroke="#f0f0f0" d="M27.66,93.53h32.49l9.1-9.08c1.4-1.4,1.41-3.7,0.01-5.1l-27.02-27.1c-1.4-1.4-3.7-1.41-5.1-0.01L14.3,75.03 c-1.41,1.4-1.41,3.7-0.01,5.1L27.66,93.53L27.66,93.53z M71.03,93.53h51.84v9.85H61.16H50.28h-12.8H25.7h-0.35L1.05,79.01 c-1.4-1.4-1.4-3.7,0.01-5.1L74.11,1.05c1.41-1.4,3.7-1.4,5.1,0.01l39.62,39.72c1.4,1.4,1.4,3.7-0.01,5.1L71.03,93.53L71.03,93.53z"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 749 B |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg height="21" viewBox="0 0 21 21" width="21" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#f0f0f0" stroke-linecap="round" stroke-linejoin="round" transform="translate(2 2)"><path d="m8.24920737-.79402796c1.17157287 0 2.12132033.94974747 2.12132033 2.12132034v13.43502882l-2.12132033 3.5355339-2.08147546-3.495689-.03442539-13.47488064c-.00298547-1.16857977.94191541-2.11832105 2.11049518-2.12130651.00180188-.00000461.00360378-.00000691.00540567-.00000691z" transform="matrix(.70710678 .70710678 -.70710678 .70710678 8.605553 -3.271644)"/><path d="m13.5 4.5 1 1"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 611 B |
|
|
@ -1,18 +0,0 @@
|
||||||
from __future__ import print_function
|
|
||||||
from numpy import *
|
|
||||||
|
|
||||||
|
|
||||||
# evaluates cubic bezier at t, return point
|
|
||||||
def q(ctrlPoly, t):
|
|
||||||
return (1.0-t)**3 * ctrlPoly[0] + 3*(1.0-t)**2 * t * ctrlPoly[1] + 3*(1.0-t)* t**2 * ctrlPoly[2] + t**3 * ctrlPoly[3]
|
|
||||||
|
|
||||||
|
|
||||||
# evaluates cubic bezier first derivative at t, return point
|
|
||||||
def qprime(ctrlPoly, t):
|
|
||||||
return 3*(1.0-t)**2 * (ctrlPoly[1]-ctrlPoly[0]) + 6*(1.0-t) * t * (ctrlPoly[2]-ctrlPoly[1]) + 3*t**2 * (ctrlPoly[3]-ctrlPoly[2])
|
|
||||||
|
|
||||||
|
|
||||||
# evaluates cubic bezier second derivative at t, return point
|
|
||||||
def qprimeprime(ctrlPoly, t):
|
|
||||||
return 6*(1.0-t) * (ctrlPoly[2]-2*ctrlPoly[1]+ctrlPoly[0]) + 6*(t) * (ctrlPoly[3]-2*ctrlPoly[2]+ctrlPoly[1])
|
|
||||||
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import numpy as np
|
|
||||||
import csv
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
def import_csv_data(filename):
|
|
||||||
stylus_x = []
|
|
||||||
stylus_y = []
|
|
||||||
stylus_pressure = []
|
|
||||||
time = []
|
|
||||||
with open(filename) as csv_fp:
|
|
||||||
reader = csv.reader(csv_fp)
|
|
||||||
next(reader) # skip header
|
|
||||||
for row in reader:
|
|
||||||
for i, v in enumerate([stylus_x, stylus_y]): #, stylus_pressure, time]):
|
|
||||||
v.append(float(row[i]))
|
|
||||||
stylus_pos = np.array([stylus_x, stylus_y])
|
|
||||||
time = np.array(time)
|
|
||||||
return time, stylus_pos
|
|
||||||
|
|
@ -1,217 +0,0 @@
|
||||||
x,y,pressure,time_usec
|
|
||||||
327.70943254927323,457.5467473524962,0.36800976800976803,13341423266
|
|
||||||
327.04769037001734,459.1733736762481,0.3916971916971917,13341433267
|
|
||||||
326.09184055553663,461.7295007564297,0.4129426129426129,13341438262
|
|
||||||
324.9521734690404,465.6798789712557,0.4358974358974359,13341448260
|
|
||||||
323.8860332913504,470.2111951588502,0.463003663003663,13341453253
|
|
||||||
322.9669469312728,475.6720121028744,0.4954822954822955,13341463263
|
|
||||||
322.08462402559826,482.0623298033283,0.525030525030525,13341468263
|
|
||||||
321.23906457432685,489.2659606656581,0.5470085470085471,13341478267
|
|
||||||
320.43026857745855,497.28290468986387,0.5641025641025641,13341483253
|
|
||||||
319.8420533070089,504.95128593040846,0.5711843711843712,13341493262
|
|
||||||
319.43765530857473,511.80635400907715,0.5758241758241758,13341498256
|
|
||||||
319.547945671784,515.7567322239032,0.5782661782661783,13341507264
|
|
||||||
320.31997821424926,516.4538577912255,0.5804639804639805,13341512275
|
|
||||||
322.04786057119514,512.9682299546142,0.5846153846153846,13341522420
|
|
||||||
324.7683561970249,505.2998487140696,0.5914529914529915,13341527280
|
|
||||||
328.3711747285291,494.4944024205749,0.6034188034188034,13341537276
|
|
||||||
332.67249889369236,481.48139183055974,0.6173382173382174,13341542263
|
|
||||||
336.97382305885554,468.4683812405446,0.6322344322344322,13341552267
|
|
||||||
341.01780304319703,457.08199697428137,0.6456654456654457,13341557287
|
|
||||||
343.8485890322361,450.69167927382756,0.6598290598290598,13341567268
|
|
||||||
344.95149266432924,450.9240544629349,0.6722832722832723,13341572419
|
|
||||||
343.811825577833,460.9161875945537,0.6825396825396826,13341582312
|
|
||||||
304.2543486400926,675.0499243570348,0.6991452991452991,13341617304
|
|
||||||
301.57061646866595,689.9219364599093,0.7137973137973138,13341627298
|
|
||||||
298.2619055723866,708.1633888048411,0.7299145299145299,13341632308
|
|
||||||
295.10024849371956,725.7077155824509,0.7555555555555555,13341642309
|
|
||||||
294.3282159512544,730.9361573373676,0.7892551892551892,13341647270
|
|
||||||
295.87228103618475,723.9649016641453,0.8178266178266178,13341657427
|
|
||||||
299.8794975661232,705.026323751891,0.8459096459096459,13341662307
|
|
||||||
306.0925213602478,677.0251134644478,0.8656898656898657,13341672414
|
|
||||||
313.9966640569153,642.8659606656581,0.8776556776556776,13341677324
|
|
||||||
322.56254893283864,608.8229954614221,0.8815628815628815,13341686308
|
|
||||||
330.9078530823433,578.8465960665658,0.8815628815628815,13341691312
|
|
||||||
337.56203832930527,558.3975794251135,0.8761904761904762,13341701310
|
|
||||||
341.9368894032747,549.2187594553707,0.8691086691086691,13341706456
|
|
||||||
343.51771794260816,552.8205748865355,0.8522588522588522,13341716309
|
|
||||||
342.1574701296933,569.3192133131619,0.8305250305250306,13341721310
|
|
||||||
338.2237805085611,597.4366111951589,0.802930402930403,13341731267
|
|
||||||
332.0842836232427,634.8490166414523,0.7763125763125763,13341736288
|
|
||||||
324.69482928821867,676.7927382753404,0.7587301587301587,13341746310
|
|
||||||
316.717159682745,720.1307110438729,0.7467643467643468,13341751319
|
|
||||||
309.4747591653334,758.2402420574887,0.7567765567765568,13341761311
|
|
||||||
303.5926064608367,789.6108925869894,0.7721611721611722,13341766301
|
|
||||||
299.62215338530143,809.943721633888,0.7892551892551892,13341776418
|
|
||||||
297.8942710283555,818.4254160363087,0.8136752136752137,13341781299
|
|
||||||
298.4824862988052,814.8236006051437,0.8344322344322345,13341791355
|
|
||||||
301.42356265105354,798.3249621785174,0.852014652014652,13341796307
|
|
||||||
306.8277904483099,770.6723146747353,0.8695970695970696,13341806489
|
|
||||||
314.5113524185587,733.2599092284418,0.8808302808302808,13341811286
|
|
||||||
323.9963236545597,690.7352496217852,0.8847374847374847,13341821318
|
|
||||||
334.65772543146,647.0487140695915,0.884981684981685,13341826271
|
|
||||||
344.84120230111995,608.5906202723147,0.8757020757020757,13341836311
|
|
||||||
352.92916226980293,581.518910741301,0.8605616605616606,13341850311
|
|
||||||
358.6274977022841,565.1364599092284,0.8398046398046398,13341851308
|
|
||||||
361.49504714572623,561.4184568835099,0.8207570207570207,13341855333
|
|
||||||
361.2009395105014,572.2239031770046,0.7980463980463981,13341865311
|
|
||||||
358.4069169758655,595.1128593040847,0.7716727716727717,13341870306
|
|
||||||
352.41447390815944,632.5252647503783,0.7492063492063492,13341880311
|
|
||||||
343.8485890322361,681.5564296520423,0.7267399267399267,13341885301
|
|
||||||
334.0327467066072,735.0027231467474,0.7142857142857143,13341895309
|
|
||||||
324.1433774721721,788.1004538577912,0.717948717948718,13341900305
|
|
||||||
315.72454641386116,833.8783661119516,0.7321123321123321,13341910330
|
|
||||||
309.40123225652724,868.8508320726172,0.746031746031746,13341915309
|
|
||||||
305.7248868162168,889.532223903177,0.7621489621489621,13341925376
|
|
||||||
304.73227354733297,895.1092284417549,0.7753357753357754,13341930261
|
|
||||||
306.49691935868196,885.3494704992436,0.7851037851037851,13341940453
|
|
||||||
310.9452973414576,861.0662632375189,0.7956043956043956,13341945270
|
|
||||||
318.00388058685365,824.8157337367625,0.8058608058608059,13341955322
|
|
||||||
327.56237873166083,778.8054462934947,0.8126984126984127,13341960315
|
|
||||||
338.8487592334139,728.8447806354009,0.800976800976801,13341970346
|
|
||||||
351.0909895496477,678.3031770045386,0.7831501831501831,13341975305
|
|
||||||
362.6347142322225,633.4547655068079,0.7511599511599512,13341985350
|
|
||||||
372.5608469210607,597.78517397882,0.7157509157509158,13341990416
|
|
||||||
379.39884944003813,575.8257186081695,0.6910866910866911,13342000315
|
|
||||||
382.85461415392996,569.3192133131619,0.6725274725274726,13342005346
|
|
||||||
382.7443237907206,579.7760968229954,0.6678876678876678,13342015312
|
|
||||||
379.06797835041016,606.9639939485628,0.6678876678876678,13342020307
|
|
||||||
372.78142764747935,648.7915279878971,0.67008547008547,13342029298
|
|
||||||
364.10525240834664,703.0511346444781,0.6708180708180708,13342034303
|
|
||||||
354.17911971950844,765.3276853252647,0.673992673992674,13342044314
|
|
||||||
343.811825577833,832.2517397881996,0.6727716727716728,13342049311
|
|
||||||
333.92245634339787,897.2006051437216,0.6896214896214896,13342059370
|
|
||||||
325.28304455866834,956.8048411497731,0.715018315018315,13342064312
|
|
||||||
318.99649385573747,1003.8608169440242,0.7357753357753358,13342074436
|
|
||||||
315.320148415427,1035.4638426626325,0.7604395604395604,13342079266
|
|
||||||
314.91575041699286,1048.593040847201,0.7697191697191698,13342089349
|
|
||||||
317.89359022364437,1042.2027231467473,0.7736263736263737,13342094311
|
|
||||||
324.2536678353814,1016.7576399394857,0.7829059829059829,13342104309
|
|
||||||
333.959219797801,973.3034795763994,0.7907203907203907,13342109305
|
|
||||||
346.78966538448446,915.5582450832072,0.796092796092796,13342119267
|
|
||||||
362.450896960207,846.5428139183056,0.7987789987789988,13342124454
|
|
||||||
380.0238281648909,773.925567322239,0.7987789987789988,13342134314
|
|
||||||
398.6628995472649,702.1216338880484,0.7992673992673993,13342139461
|
|
||||||
416.30935766075504,639.728895612708,0.7914529914529914,13342149298
|
|
||||||
431.89706232767134,590.1167927382753,0.7772893772893773,13342154306
|
|
||||||
443.5510773734554,558.3975794251135,0.7611721611721611,13342164267
|
|
||||||
450.7199509820608,546.6626323751891,0.7450549450549451,13342169350
|
|
||||||
452.85223133744086,556.190015128593,0.7372405372405373,13342179316
|
|
||||||
450.09497225720804,586.5149773071105,0.7365079365079366,13342184312
|
|
||||||
443.07315246621505,637.753706505295,0.7382173382173383,13342193328
|
|
||||||
432.52204105252406,706.5367624810892,0.7391941391941392,13342198308
|
|
||||||
420.16952037308096,786.9385779122541,0.7396825396825397,13342208340
|
|
||||||
406.787622970351,874.5440242057489,0.7450549450549451,13342213313
|
|
||||||
394.5821561085203,957.8505295007565,0.7565323565323565,13342223406
|
|
||||||
384.32515233005415,1032.3267776096823,0.76996336996337,13342228312
|
|
||||||
377.229805630255,1089.2586989409986,0.7836385836385836,13342238315
|
|
||||||
373.77404091636316,1126.671104387292,0.7956043956043956,13342243440
|
|
||||||
374.06814855158797,1141.0783661119515,0.8075702075702076,13342253311
|
|
||||||
378.07536508152634,1132.3642965204235,0.8214896214896215,13342258383
|
|
||||||
385.7589270517752,1101.6907715582452,0.8378510378510379,13342268290
|
|
||||||
397.0085440991252,1049.871104387292,0.8515262515262515,13342273325
|
|
||||||
411.4933451339483,983.1794251134645,0.8556776556776556,13342283297
|
|
||||||
429.06627633863224,905.7984871406959,0.8556776556776556,13342288288
|
|
||||||
448.14650917384347,828.0689863842663,0.8241758241758241,13342298297
|
|
||||||
467.66790346189197,755.335552193646,0.778998778998779,13342303281
|
|
||||||
485.0937808489635,696.6608169440242,0.705982905982906,13342313370
|
|
||||||
499.0271300677401,655.7627836611196,0.6344322344322344,13342318245
|
|
||||||
507.5194880348572,637.1727685325264,0.5975579975579975,13342328257
|
|
||||||
509.9458760254621,641.4717095310136,0.581929181929182,13342333288
|
|
||||||
506.60040167477956,668.0786686838125,0.5982905982905983,13342343294
|
|
||||||
497.9242264356469,716.0641452344931,0.6161172161172161,13342348315
|
|
||||||
485.53494230180075,781.0130105900151,0.638095238095238,13342358345
|
|
||||||
470.75603363175276,859.7881996974281,0.663980463980464,13342363306
|
|
||||||
455.42567314565815,945.8832072617247,0.705006105006105,13342373416
|
|
||||||
440.8673452020288,1032.907715582451,0.7496947496947497,13342377268
|
|
||||||
428.99274942982606,1111.1019667170954,0.7882783882783883,13342387310
|
|
||||||
420.2430472818872,1175.1213313161877,0.8126984126984127,13342392288
|
|
||||||
415.4637982094836,1217.8783661119517,0.8234432234432234,13342402308
|
|
||||||
414.6182387582122,1238.327382753404,0.8324786324786325,13342407319
|
|
||||||
417.44902474725126,1236.0036308623298,0.8407814407814408,13342417305
|
|
||||||
424.02968308540693,1210.6747352496218,0.8512820512820513,13342422365
|
|
||||||
434.1028695918576,1164.4320726172466,0.8564102564102564,13342432350
|
|
||||||
447.63182081220003,1100.2965204236007,0.8505494505494505,13342437297
|
|
||||||
464.06508493038774,1022.7993948562784,0.8273504273504273,13342447267
|
|
||||||
482.7041563127617,938.0986384266263,0.7887667887667887,13342452304
|
|
||||||
502.04173332879463,856.1863842662632,0.736996336996337,13342462355
|
|
||||||
520.9381488919903,780.5482602118003,0.6879120879120879,13342467297
|
|
||||||
537.0405419205501,722.2220877458396,0.6432234432234433,13342477326
|
|
||||||
549.3930625999932,683.647806354009,0.6180708180708181,13342482304
|
|
||||||
556.1207747557613,669.2405446293494,0.6188034188034188,13342492332
|
|
||||||
556.7825169350172,679.348865355522,0.6273504273504273,13342497317
|
|
||||||
552.5547196786601,710.8357034795764,0.652991452991453,13342522397
|
|
||||||
545.6064267964734,751.5013615733736,0.676923076923077,13342523321
|
|
||||||
538.3272628246588,792.8641452344932,0.6927960927960928,13342524293
|
|
||||||
527.4820437757429,856.7673222390317,0.7142857142857143,13342527322
|
|
||||||
514.3942540082377,937.1691376701966,0.7440781440781441,13342537266
|
|
||||||
498.3653878884842,1039.995158850227,0.7733821733821734,13342541322
|
|
||||||
482.9615004935834,1143.7506807866869,0.7985347985347986,13342551268
|
|
||||||
473.1088947135514,1219.2726172465962,0.8168498168498168,13342556358
|
|
||||||
469.35902236443474,1258.6602118003025,0.8234432234432234,13342566319
|
|
||||||
471.71188344623346,1260.054462934947,0.8288156288156289,13342571311
|
|
||||||
477.70432651393946,1239.0245083207262,0.8356532356532357,13342581319
|
|
||||||
487.26282465874664,1195.2217851739788,0.843956043956044,13342586303
|
|
||||||
500.1667971542363,1133.758547655068,0.8449328449328449,13342596316
|
|
||||||
516.3059536371992,1058.7013615733738,0.8300366300366301,13342601338
|
|
||||||
534.7244442931545,975.9757942511346,0.8012210012210013,13342611316
|
|
||||||
554.7605269428465,893.2502269288956,0.7714285714285715,13342616314
|
|
||||||
574.0613405044763,821.0977307110438,0.7326007326007326,13342626316
|
|
||||||
591.2666371651292,763.0039334341906,0.7013431013431013,13342631310
|
|
||||||
603.6559212989754,728.7285930408472,0.693040293040293,13342641293
|
|
||||||
610.3836334547435,719.5497730711044,0.6862026862026862,13342646315
|
|
||||||
610.6777410899683,736.3969742813919,0.693040293040293,13342656373
|
|
||||||
605.0529325662934,779.1540090771558,0.7072039072039072,13342661318
|
|
||||||
595.2370902406644,841.0819969742814,0.7128205128205128,13342671315
|
|
||||||
582.2963542907717,918.6953101361573,0.715018315018315,13342676322
|
|
||||||
568.9144568880416,1002.6989409984872,0.7194139194139194,13342686302
|
|
||||||
555.9369574837458,1087.6320726172466,0.7296703296703296,13342691308
|
|
||||||
545.2387922524424,1165.3615733736763,0.7509157509157509,13342701305
|
|
||||||
537.371413010178,1229.8456883509834,0.7716727716727717,13342706380
|
|
||||||
532.8127446641931,1275.3912254160364,0.7909645909645909,13342715268
|
|
||||||
531.8936583041154,1300.3715582450832,0.8075702075702076,13342720310
|
|
||||||
534.5773904755421,1302.6953101361573,0.8158730158730159,13342730323
|
|
||||||
540.9374680872792,1283.2919818456883,0.827106227106227,13342735300
|
|
||||||
550.7900738673112,1243.3234493192133,0.8368742368742369,13342745311
|
|
||||||
563.9513905436226,1185.2296520423602,0.8422466422466423,13342750308
|
|
||||||
580.4581815706165,1113.3095310136157,0.8454212454212454,13342760264
|
|
||||||
599.9428124042619,1031.7458396369138,0.8461538461538461,13342765320
|
|
||||||
621.3391428668687,951.2278366111951,0.8310134310134311,13342775326
|
|
||||||
643.7280865983593,875.1249621785174,0.8036630036630037,13342780312
|
|
||||||
663.4332981584232,816.1016641452345,0.7438339438339439,13342790325
|
|
||||||
678.8739490077271,778.4568835098336,0.6852258852258852,13342795313
|
|
||||||
687.7707049732784,766.4895612708018,0.6427350427350428,13342805276
|
|
||||||
689.6088776934337,781.5939485627837,0.611965811965812,13342810273
|
|
||||||
685.491370800286,819.9358547655069,0.6144078144078144,13342820313
|
|
||||||
676.4107975627192,878.4944024205749,0.6158730158730159,13342825323
|
|
||||||
664.7935459713382,949.1364599092284,0.6297924297924298,13342835327
|
|
||||||
651.8528100214453,1027.7954614220878,0.6525030525030525,13342840383
|
|
||||||
639.8311604316302,1106.2220877458396,0.6998778998778998,13342850316
|
|
||||||
629.5741566531641,1179.5364599092284,0.7516483516483516,13342855320
|
|
||||||
622.4788099533649,1241.115885022693,0.7914529914529914,13342865317
|
|
||||||
618.9495183306668,1287.1261724659607,0.8266178266178266,13342870306
|
|
||||||
619.5744970555196,1310.9446293494705,0.842979242979243,13342879268
|
|
||||||
624.5007999455356,1311.525567322239,0.8578754578754578,13342884305
|
|
||||||
633.6549000919086,1288.0556732223904,0.873992673992674,13342894318
|
|
||||||
647.0735609490417,1241.5806354009078,0.8886446886446886,13342899308
|
|
||||||
664.3523845185009,1178.2583963691377,0.8986568986568987,13342909263
|
|
||||||
684.8296286210301,1101.8069591527988,0.9037851037851038,13342914264
|
|
||||||
707.6229703509548,1021.753706505295,0.8964590964590965,13342924326
|
|
||||||
731.7030329849882,943.3270801815431,0.873992673992674,13342929322
|
|
||||||
754.8640092589441,875.705900151286,0.8068376068376069,13342939295
|
|
||||||
776.3706300847601,822.4919818456883,0.7152625152625153,13342944339
|
|
||||||
793.4656363822038,788.6813918305597,0.6205128205128205,13342954311
|
|
||||||
805.2667052456003,775.900756429652,0.5377289377289377,13342959312
|
|
||||||
810.8179868604691,784.6148260211801,0.5111111111111111,13342969315
|
|
||||||
809.9724274091976,814.3588502269289,0.5238095238095238,13342974319
|
|
||||||
803.9064574326854,862.3443267776097,0.575091575091575,13342984318
|
|
||||||
793.7597440174286,926.2475037821482,0.6371184371184371,13342989318
|
|
||||||
781.7013309732104,1000.7237518910741,0.7040293040293041,13342999313
|
|
||||||
768.6503046601083,1082.4036308623297,0.7645909645909645,13343004304
|
|
||||||
756.9227627055179,1163.6187594553708,0.8075702075702076,13343014303
|
|
||||||
747.1436838342921,1240.1863842662633,0.841025641025641,13343019254
|
|
||||||
740.5630254961364,1304.4381240544628,0.8517704517704517,13343029277
|
|
||||||
737.8057664159036,1353.469288956127,0.8527472527472527,13343034308
|
|
||||||
739.1292507744154,1383.5618759455372,0.8312576312576313,13343044329
|
|
||||||
744.6805323892842,1394.0187594553706,0.7819291819291819,13343049309
|
|
||||||
754.496374714913,1384.9561270801814,0.765079365079365,13343058360
|
|
||||||
|
|
|
@ -1,162 +0,0 @@
|
||||||
""" Python implementation of
|
|
||||||
Algorithm for Automatically Fitting Digitized Curves
|
|
||||||
by Philip J. Schneider
|
|
||||||
"Graphics Gems", Academic Press, 1990
|
|
||||||
"""
|
|
||||||
from __future__ import print_function
|
|
||||||
from numpy import *
|
|
||||||
import bezier
|
|
||||||
|
|
||||||
|
|
||||||
# Fit one (ore more) Bezier curves to a set of points
|
|
||||||
def fitCurve(points, maxError):
|
|
||||||
leftTangent = normalize(points[1] - points[0])
|
|
||||||
rightTangent = normalize(points[-2] - points[-1])
|
|
||||||
return fitCubic(points, leftTangent, rightTangent, maxError)
|
|
||||||
|
|
||||||
|
|
||||||
def fitCubic(points, leftTangent, rightTangent, error):
|
|
||||||
# Use heuristic if region only has two points in it
|
|
||||||
if (len(points) == 2):
|
|
||||||
dist = linalg.norm(points[0] - points[1]) / 3.0
|
|
||||||
bezCurve = [points[0], points[0] + leftTangent * dist, points[1] + rightTangent * dist, points[1]]
|
|
||||||
return [bezCurve]
|
|
||||||
|
|
||||||
# Parameterize points, and attempt to fit curve
|
|
||||||
u = chordLengthParameterize(points)
|
|
||||||
bezCurve = generateBezier(points, u, leftTangent, rightTangent)
|
|
||||||
# Find max deviation of points to fitted curve
|
|
||||||
maxError, splitPoint = computeMaxError(points, bezCurve, u)
|
|
||||||
if maxError < error:
|
|
||||||
return [bezCurve]
|
|
||||||
|
|
||||||
# If error not too large, try some reparameterization and iteration
|
|
||||||
if maxError < error**2:
|
|
||||||
for i in range(20):
|
|
||||||
uPrime = reparameterize(bezCurve, points, u)
|
|
||||||
bezCurve = generateBezier(points, uPrime, leftTangent, rightTangent)
|
|
||||||
maxError, splitPoint = computeMaxError(points, bezCurve, uPrime)
|
|
||||||
if maxError < error:
|
|
||||||
return [bezCurve]
|
|
||||||
u = uPrime
|
|
||||||
|
|
||||||
# Fitting failed -- split at max error point and fit recursively
|
|
||||||
beziers = []
|
|
||||||
centerTangent = normalize(points[splitPoint-1] - points[splitPoint+1])
|
|
||||||
beziers += fitCubic(points[:splitPoint+1], leftTangent, centerTangent, error)
|
|
||||||
beziers += fitCubic(points[splitPoint:], -centerTangent, rightTangent, error)
|
|
||||||
|
|
||||||
return beziers
|
|
||||||
|
|
||||||
|
|
||||||
def generateBezier(points, parameters, leftTangent, rightTangent):
|
|
||||||
bezCurve = [points[0], None, None, points[-1]]
|
|
||||||
|
|
||||||
# compute the A's
|
|
||||||
A = zeros((len(parameters), 2, 2))
|
|
||||||
for i, u in enumerate(parameters):
|
|
||||||
A[i][0] = leftTangent * 3*(1-u)**2 * u
|
|
||||||
A[i][1] = rightTangent * 3*(1-u) * u**2
|
|
||||||
|
|
||||||
# Create the C and X matrices
|
|
||||||
C = zeros((2, 2))
|
|
||||||
X = zeros(2)
|
|
||||||
|
|
||||||
for i, (point, u) in enumerate(zip(points, parameters)):
|
|
||||||
C[0][0] += dot(A[i][0], A[i][0])
|
|
||||||
C[0][1] += dot(A[i][0], A[i][1])
|
|
||||||
C[1][0] += dot(A[i][0], A[i][1])
|
|
||||||
C[1][1] += dot(A[i][1], A[i][1])
|
|
||||||
|
|
||||||
tmp = point - bezier.q([points[0], points[0], points[-1], points[-1]], u)
|
|
||||||
|
|
||||||
X[0] += dot(A[i][0], tmp)
|
|
||||||
X[1] += dot(A[i][1], tmp)
|
|
||||||
|
|
||||||
# Compute the determinants of C and X
|
|
||||||
det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1]
|
|
||||||
det_C0_X = C[0][0] * X[1] - C[1][0] * X[0]
|
|
||||||
det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1]
|
|
||||||
|
|
||||||
# Finally, derive alpha values
|
|
||||||
alpha_l = 0.0 if det_C0_C1 == 0 else det_X_C1 / det_C0_C1
|
|
||||||
alpha_r = 0.0 if det_C0_C1 == 0 else det_C0_X / det_C0_C1
|
|
||||||
|
|
||||||
# If alpha negative, use the Wu/Barsky heuristic (see text) */
|
|
||||||
# (if alpha is 0, you get coincident control points that lead to
|
|
||||||
# divide by zero in any subsequent NewtonRaphsonRootFind() call. */
|
|
||||||
segLength = linalg.norm(points[0] - points[-1])
|
|
||||||
epsilon = 1.0e-6 * segLength
|
|
||||||
if alpha_l < epsilon or alpha_r < epsilon:
|
|
||||||
# fall back on standard (probably inaccurate) formula, and subdivide further if needed.
|
|
||||||
bezCurve[1] = bezCurve[0] + leftTangent * (segLength / 3.0)
|
|
||||||
bezCurve[2] = bezCurve[3] + rightTangent * (segLength / 3.0)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# First and last control points of the Bezier curve are
|
|
||||||
# positioned exactly at the first and last data points
|
|
||||||
# Control points 1 and 2 are positioned an alpha distance out
|
|
||||||
# on the tangent vectors, left and right, respectively
|
|
||||||
bezCurve[1] = bezCurve[0] + leftTangent * alpha_l
|
|
||||||
bezCurve[2] = bezCurve[3] + rightTangent * alpha_r
|
|
||||||
|
|
||||||
return bezCurve
|
|
||||||
|
|
||||||
|
|
||||||
def reparameterize(bezier, points, parameters):
|
|
||||||
return [newtonRaphsonRootFind(bezier, point, u) for point, u in zip(points, parameters)]
|
|
||||||
|
|
||||||
|
|
||||||
def newtonRaphsonRootFind(bez, point, u):
|
|
||||||
"""
|
|
||||||
Newton's root finding algorithm calculates f(x)=0 by reiterating
|
|
||||||
x_n+1 = x_n - f(x_n)/f'(x_n)
|
|
||||||
|
|
||||||
We are trying to find curve parameter u for some point p that minimizes
|
|
||||||
the distance from that point to the curve. Distance point to curve is d=q(u)-p.
|
|
||||||
At minimum distance the point is perpendicular to the curve.
|
|
||||||
We are solving
|
|
||||||
f = q(u)-p * q'(u) = 0
|
|
||||||
with
|
|
||||||
f' = q'(u) * q'(u) + q(u)-p * q''(u)
|
|
||||||
|
|
||||||
gives
|
|
||||||
u_n+1 = u_n - |q(u_n)-p * q'(u_n)| / |q'(u_n)**2 + q(u_n)-p * q''(u_n)|
|
|
||||||
"""
|
|
||||||
d = bezier.q(bez, u)-point
|
|
||||||
numerator = (d * bezier.qprime(bez, u)).sum()
|
|
||||||
denominator = (bezier.qprime(bez, u)**2 + d * bezier.qprimeprime(bez, u)).sum()
|
|
||||||
|
|
||||||
if denominator == 0.0:
|
|
||||||
return u
|
|
||||||
else:
|
|
||||||
return u - numerator/denominator
|
|
||||||
|
|
||||||
|
|
||||||
def chordLengthParameterize(points):
|
|
||||||
u = [0.0]
|
|
||||||
for i in range(1, len(points)):
|
|
||||||
u.append(u[i-1] + linalg.norm(points[i] - points[i-1]))
|
|
||||||
|
|
||||||
for i, _ in enumerate(u):
|
|
||||||
u[i] = u[i] / u[-1]
|
|
||||||
|
|
||||||
return u
|
|
||||||
|
|
||||||
|
|
||||||
def computeMaxError(points, bez, parameters):
|
|
||||||
maxDist = 0.0
|
|
||||||
splitPoint = len(points)/2
|
|
||||||
for i, (point, u) in enumerate(zip(points, parameters)):
|
|
||||||
dist = linalg.norm(bezier.q(bez, u)-point)**2
|
|
||||||
if dist > maxDist:
|
|
||||||
maxDist = dist
|
|
||||||
splitPoint = i
|
|
||||||
|
|
||||||
return maxDist, splitPoint
|
|
||||||
|
|
||||||
|
|
||||||
def normalize(v):
|
|
||||||
return v / linalg.norm(v)
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,205 +0,0 @@
|
||||||
x,y,pressure,time_usec
|
|
||||||
18.822888654389487,97.13282904689864,0.10256410256410256,11399014806
|
|
||||||
18.492017564761547,95.62239031770045,0.1111111111111111,11399024788
|
|
||||||
18.01409265752119,93.53101361573374,0.12087912087912088,11399029781
|
|
||||||
17.572931204683936,91.0910741301059,0.13333333333333333,11399039790
|
|
||||||
17.2788235694591,88.99969742813919,0.14481074481074482,11399044778
|
|
||||||
17.168533206249787,87.60544629349471,0.15873015873015872,11399054783
|
|
||||||
17.09500629744358,86.90832072617246,0.1728937728937729,11399059831
|
|
||||||
17.09500629744358,86.90832072617246,0.1838827838827839,11399069794
|
|
||||||
17.20529666065289,87.83782148260212,0.19462759462759463,11399074789
|
|
||||||
17.42587738707152,89.58063540090771,0.20415140415140415,11399084790
|
|
||||||
17.75674847669946,92.25295007564297,0.21343101343101342,11399089814
|
|
||||||
18.197909929536713,95.50620272314674,0.22271062271062272,11399099785
|
|
||||||
18.74936174558328,99.57276853252648,0.231990231990232,11399104778
|
|
||||||
19.374340470436056,104.33645990922844,0.24151404151404152,11399114830
|
|
||||||
20.072846104095042,109.56490166414524,0.252014652014652,11399119826
|
|
||||||
20.80811519215713,115.25809379727686,0.26544566544566545,11399129782
|
|
||||||
21.543384280219218,121.4160363086233,0.28034188034188035,11399133889
|
|
||||||
22.352180277087516,127.92254160363086,0.29572649572649573,11399143784
|
|
||||||
23.308030091568234,134.89379727685326,0.30964590964590966,11399148872
|
|
||||||
24.41093372366137,142.32980332829047,0.3189255189255189,11399158805
|
|
||||||
25.624127718963816,150.46293494704992,0.32576312576312577,11399163785
|
|
||||||
26.874085168669367,159.2931921331316,0.3286935286935287,11399173825
|
|
||||||
28.23433298158423,168.47201210287443,0.3308913308913309,11399178841
|
|
||||||
29.70487115770841,177.65083207261725,0.3316239316239316,11399188784
|
|
||||||
31.285699697041903,186.94583963691377,0.3326007326007326,11399193778
|
|
||||||
32.9400551451816,196.2408472012103,0.33357753357753356,11399203784
|
|
||||||
34.63117404772441,205.6520423600605,0.3343101343101343,11399208778
|
|
||||||
36.35905640467032,215.64417549167928,0.3347985347985348,11399218784
|
|
||||||
38.12370221601933,225.75249621785173,0.335042735042735,11399223785
|
|
||||||
39.81482111856214,236.32556732223904,0.335042735042735,11399233791
|
|
||||||
41.50594002110495,247.01482602118003,0.33528693528693526,11399238781
|
|
||||||
43.01324165163223,257.82027231467475,0.33577533577533575,11399248924
|
|
||||||
44.33672601014399,268.39334341906203,0.33601953601953605,11399253784
|
|
||||||
45.54992000544644,278.7340393343419,0.33650793650793653,11399263780
|
|
||||||
46.61606018313647,288.49379727685323,0.336996336996337,11399268789
|
|
||||||
47.7189638152296,298.1373676248109,0.33724053724053726,11399278784
|
|
||||||
48.85863090172584,307.7809379727685,0.337973137973138,11399283935
|
|
||||||
50.10858835143139,317.42450832072615,0.3384615384615385,11399293798
|
|
||||||
51.43207270994315,327.41664145234495,0.3391941391941392,11399298856
|
|
||||||
52.86584743166423,337.8735249621785,0.3396825396825397,11399307786
|
|
||||||
54.37314906219151,348.6789712556732,0.3401709401709402,11399312775
|
|
||||||
55.84368723831569,359.94916792738275,0.3409035409035409,11399322815
|
|
||||||
57.27746196003676,371.4517397881997,0.3413919413919414,11399327786
|
|
||||||
58.527419409742315,382.7219364599092,0.3418803418803419,11399337784
|
|
||||||
59.667086496238554,393.6435703479576,0.3423687423687424,11399342827
|
|
||||||
60.659699765122376,403.86807866868384,0.3431013431013431,11399352805
|
|
||||||
61.505259216393775,413.27927382753404,0.34383394383394383,11399357798
|
|
||||||
62.31405521326207,421.76096822995464,0.3452991452991453,11399367845
|
|
||||||
63.04932430132416,429.1969742813918,0.347008547008547,11399372781
|
|
||||||
63.67430302617694,435.5872919818457,0.3496947496947497,11399382927
|
|
||||||
64.26251829662661,441.0481089258699,0.35262515262515265,11399387804
|
|
||||||
64.77720665827007,445.81180030257184,0.35702075702075703,11399397779
|
|
||||||
65.18160465670422,450.22692889561273,0.3619047619047619,11399402781
|
|
||||||
65.58600265513837,454.64205748865356,0.3672771672771673,11399412781
|
|
||||||
65.95363719916942,459.1733736762481,0.3728937728937729,11399417950
|
|
||||||
66.39479865200667,463.58850226928894,0.3775335775335775,11399427783
|
|
||||||
66.83596010484392,468.0036308623298,0.38168498168498166,11399432864
|
|
||||||
67.31388501208428,471.6054462934947,0.38412698412698415,11399442791
|
|
||||||
67.79180991932463,474.39394856278363,0.3855921855921856,11399447790
|
|
||||||
68.269734826565,476.13676248108925,0.3855921855921856,11399457805
|
|
||||||
68.74765973380536,476.7177004538578,0.3855921855921856,11399462805
|
|
||||||
69.26234809544881,476.02057488653554,0.38534798534798537,11399471790
|
|
||||||
69.8137999114954,474.2777609682299,0.38534798534798537,11399477776
|
|
||||||
70.36525172754196,471.02450832072617,0.3860805860805861,11399486793
|
|
||||||
70.91670354358853,466.60937972768534,0.38705738705738707,11399491782
|
|
||||||
71.50491881403819,461.0323751891074,0.3882783882783883,11399501785
|
|
||||||
72.09313408448786,454.64205748865356,0.38925518925518926,11399506781
|
|
||||||
72.75487626374374,447.67080181543116,0.38998778998779,11399516913
|
|
||||||
73.41661844299962,439.7700453857791,0.3904761904761905,11399521779
|
|
||||||
74.11512407665862,431.17216338880485,0.3904761904761905,11399531906
|
|
||||||
74.8871566191238,421.5285930408472,0.38974358974358975,11399536796
|
|
||||||
75.6959526159921,410.8393343419062,0.38852258852258853,11399546785
|
|
||||||
76.61503897606971,399.22057488653553,0.3882783882783883,11399551783
|
|
||||||
77.75470606256596,386.6723146747353,0.38852258852258853,11399561814
|
|
||||||
79.0046635122715,373.5431164901664,0.38974358974358975,11399566854
|
|
||||||
80.43843823399258,359.83298033282904,0.3912087912087912,11399576782
|
|
||||||
82.01926677332607,345.8904689863843,0.3921855921855922,11399581909
|
|
||||||
83.71038567586888,332.1803328290469,0.3924297924297924,11399591788
|
|
||||||
85.43826803281479,318.81875945537064,0.39267399267399267,11399596776
|
|
||||||
87.12938693535759,305.6895612708018,0.39316239316239315,11399606786
|
|
||||||
88.7837423834973,292.7927382753404,0.3934065934065934,11399611786
|
|
||||||
90.29104401402458,279.895915279879,0.3934065934065934,11399621783
|
|
||||||
91.68805528134254,266.99909228441754,0.3934065934065934,11399626785
|
|
||||||
93.0115396398543,254.45083207261726,0.3934065934065934,11399636784
|
|
||||||
94.29826054396297,242.01875945537066,0.39316239316239315,11399641782
|
|
||||||
95.51145453926542,229.81906202723147,0.39316239316239315,11399650791
|
|
||||||
96.76141198897096,217.9679273827534,0.39316239316239315,11399655781
|
|
||||||
97.90107907546721,206.69773071104387,0.3934065934065934,11399665890
|
|
||||||
99.04074616196344,196.1246596066566,0.3938949938949939,11399670781
|
|
||||||
100.07012288525037,186.48108925869894,0.39487179487179486,11399680781
|
|
||||||
100.95244579092487,177.41845688350983,0.3963369963369963,11399685800
|
|
||||||
101.68771487898697,169.16913767019668,0.39755799755799753,11399695788
|
|
||||||
102.20240324063043,161.73313161875944,0.3987789987789988,11399700837
|
|
||||||
102.53327433025837,154.87806354009078,0.4,11399710783
|
|
||||||
102.8273819654832,148.72012102874433,0.40073260073260075,11399715936
|
|
||||||
103.04796269190183,143.25930408472013,0.4014652014652015,11399725809
|
|
||||||
103.19501650951425,138.14704992435705,0.4021978021978022,11399730776
|
|
||||||
103.34207032712666,133.26717095310136,0.40293040293040294,11399740781
|
|
||||||
103.37883378152976,128.96822995461423,0.40341880341880343,11399745774
|
|
||||||
103.41559723593288,125.0178517397882,0.4039072039072039,11399755784
|
|
||||||
103.45236069033598,121.532223903177,0.40415140415140416,11399760776
|
|
||||||
103.48912414473908,118.51134644478064,0.4043956043956044,11399770786
|
|
||||||
103.6361779623515,115.72284417549167,0.4043956043956044,11399775776
|
|
||||||
103.81999523436703,113.28290468986384,0.4043956043956044,11399785786
|
|
||||||
104.07733941518876,110.95915279878972,0.4039072039072039,11399790805
|
|
||||||
104.4082105048167,109.10015128593041,0.40293040293040294,11399800868
|
|
||||||
104.77584504884774,107.70590015128593,0.40195360195360197,11399805779
|
|
||||||
105.14347959287878,106.77639939485628,0.400976800976801,11399815869
|
|
||||||
105.54787759131293,106.31164901664145,0.4,11399819779
|
|
||||||
106.02580249855329,106.42783661119516,0.3992673992673993,11399829778
|
|
||||||
106.54049086019675,107.2411497730711,0.39902319902319905,11399834782
|
|
||||||
107.20223303945264,108.9839636913767,0.3987789987789988,11399844790
|
|
||||||
107.97426558191783,111.54009077155824,0.3987789987789988,11399849782
|
|
||||||
108.89335194199543,115.02571860816944,0.3987789987789988,11399859779
|
|
||||||
109.95949211968546,119.32465960665658,0.3987789987789988,11399864803
|
|
||||||
111.17268611498791,124.20453857791226,0.39853479853479856,11399874786
|
|
||||||
112.49617047349967,129.78154311649016,0.39829059829059826,11399879783
|
|
||||||
113.92994519522075,135.8232980332829,0.3978021978021978,11399889789
|
|
||||||
115.47401028015113,142.56217851739788,0.3973137973137973,11399894806
|
|
||||||
117.12836572829085,149.88199697428138,0.3968253968253968,11399904781
|
|
||||||
118.89301153963986,158.13131618759456,0.39658119658119656,11399909776
|
|
||||||
120.69442080539197,167.19394856278367,0.39658119658119656,11399919785
|
|
||||||
122.5693569799503,177.0698940998487,0.39658119658119656,11399924798
|
|
||||||
124.51782006331484,187.7591527987897,0.39755799755799753,11399934789
|
|
||||||
126.57657350988869,199.02934947049926,0.39902319902319905,11399939776
|
|
||||||
128.70885386526874,210.88048411497732,0.4061050061050061,11399949821
|
|
||||||
130.9514245838581,223.1963691376702,0.41514041514041516,11399954781
|
|
||||||
133.1204683936413,235.97700453857792,0.42466422466422465,11399964813
|
|
||||||
135.25274874902135,249.22239031770044,0.4351648351648352,11399969797
|
|
||||||
137.3850291044014,262.93252647503783,0.44078144078144077,11399979784
|
|
||||||
139.51730945978147,276.8750378214826,0.44468864468864466,11399983821
|
|
||||||
141.83340708717705,290.9337367624811,0.44761904761904764,11399993783
|
|
||||||
144.25979507778194,304.8762481089259,0.4490842490842491,11399998845
|
|
||||||
146.79647343159616,318.70257186081693,0.45006105006105007,11400008783
|
|
||||||
149.36991523981345,332.64508320726173,0.45103785103785105,11400013775
|
|
||||||
151.90659359362766,346.5875945537065,0.452014652014652,11400023783
|
|
||||||
154.40650849303879,360.7624810892587,0.452991452991453,11400028775
|
|
||||||
156.83289648364368,374.5888048411498,0.4542124542124542,11400038784
|
|
||||||
159.22252101984546,387.950378214826,0.4554334554334554,11400043780
|
|
||||||
161.53861864724104,400.4986384266263,0.45665445665445664,11400053788
|
|
||||||
163.7444259114273,412.11739788199696,0.4581196581196581,11400058779
|
|
||||||
165.80317935800116,422.92284417549166,0.4598290598290598,11400068853
|
|
||||||
167.86193280457502,432.91497730711046,0.46153846153846156,11400073780
|
|
||||||
169.88392279674576,442.09379727685325,0.4634920634920635,11400083858
|
|
||||||
171.9794396977227,450.69167927382756,0.4652014652014652,11400088780
|
|
||||||
174.14848350750586,458.47624810892586,0.4661782661782662,11400098851
|
|
||||||
176.31752731728903,465.56369137670197,0.4669108669108669,11400103783
|
|
||||||
178.41304421826598,472.07019667170954,0.4673992673992674,11400113778
|
|
||||||
180.36150730163052,477.76338880484116,0.4678876678876679,11400118781
|
|
||||||
182.12615311297955,482.99183055975794,0.46813186813186813,11400128775
|
|
||||||
183.70698165231303,487.6393343419062,0.4683760683760684,11400133908
|
|
||||||
185.2142832828403,491.8220877458396,0.4686202686202686,11400143786
|
|
||||||
186.6848214589645,495.77246596066567,0.46886446886446886,11400148861
|
|
||||||
188.11859618068556,499.60665658093797,0.4691086691086691,11400157800
|
|
||||||
189.55237090240664,503.2084720121029,0.46935286935286935,11400163777
|
|
||||||
190.9493821697246,506.9264750378215,0.46935286935286935,11400172788
|
|
||||||
192.30962998263948,510.41210287443266,0.46935286935286935,11400177790
|
|
||||||
193.63311434115124,513.7815431164902,0.46886446886446886,11400187784
|
|
||||||
194.8463083364537,516.5700453857792,0.46813186813186813,11400192807
|
|
||||||
195.98597542294993,518.5452344931921,0.46715506715506716,11400202804
|
|
||||||
197.01535214623686,519.8232980332829,0.4661782661782662,11400207783
|
|
||||||
197.97120196071756,520.4042360060514,0.4652014652014652,11400217838
|
|
||||||
198.85352486639206,520.5204236006051,0.46422466422466424,11400222798
|
|
||||||
199.7358477720666,520.0556732223903,0.463003663003663,11400232798
|
|
||||||
200.58140722333798,519.0099848714069,0.46202686202686205,11400237782
|
|
||||||
201.4269666746094,517.034795763994,0.46202686202686205,11400247780
|
|
||||||
202.2725261258808,514.246293494705,0.46251526251526254,11400252781
|
|
||||||
203.0813221227491,510.6444780635401,0.4647130647130647,11400262783
|
|
||||||
203.81659121081117,506.22934947049924,0.46715506715506716,11400267798
|
|
||||||
204.51509684447015,500.88472012102875,0.46886446886446886,11400277785
|
|
||||||
205.14007556932293,494.7267776096823,0.4700854700854701,11400282793
|
|
||||||
205.7650542941757,487.29077155824507,0.4708180708180708,11400292813
|
|
||||||
206.3900330190285,478.6928895612708,0.4717948717948718,11400297775
|
|
||||||
207.01501174388127,468.8169440242057,0.4735042735042735,11400307809
|
|
||||||
207.67675392313714,457.6629349470499,0.47545787545787543,11400312780
|
|
||||||
208.30173264798992,445.4632375189107,0.47741147741147744,11400321785
|
|
||||||
208.8899479184396,431.9854765506808,0.4796092796092796,11400327779
|
|
||||||
209.47816318888925,417.57821482602117,0.48205128205128206,11400336784
|
|
||||||
209.99285155053272,401.8928895612708,0.4844932844932845,11400341786
|
|
||||||
210.50753991217618,385.5104387291982,0.4862026862026862,11400351782
|
|
||||||
211.02222827381965,368.66323751891076,0.4874236874236874,11400356779
|
|
||||||
211.53691663546311,351.6998487140696,0.48864468864468863,11400366862
|
|
||||||
212.05160499710658,334.96883509833583,0.4901098901098901,11400371794
|
|
||||||
212.60305681315316,318.5863842662632,0.4927960927960928,11400381836
|
|
||||||
213.1545086291997,302.43630862329803,0.4954822954822955,11400386781
|
|
||||||
213.74272389964938,286.63479576399396,0.49743589743589745,11400396785
|
|
||||||
214.40446607890527,270.94947049924355,0.49865689865689866,11400401840
|
|
||||||
215.10297171256425,255.3803328290469,0.4993894993894994,11400411780
|
|
||||||
215.87500425502944,240.27594553706504,0.5001221001221001,11400416832
|
|
||||||
216.64703679749465,225.28774583963693,0.5010989010989011,11400426792
|
|
||||||
217.38230588555672,210.5319213313162,0.5025641025641026,11400431783
|
|
||||||
218.0072846104095,196.2408472012103,0.5042735042735043,11400441782
|
|
||||||
218.48520951764985,182.1821482602118,0.5057387057387057,11400446781
|
|
||||||
218.8160806072778,169.28532526475038,0.5069597069597069,11400456789
|
|
||||||
219.07342478809954,157.78275340393344,0.5079365079365079,11400461784
|
|
||||||
219.18371515130886,147.79062027231467,0.5089133089133089,11400471781
|
|
||||||
219.22047860571195,139.4251134644478,0.5096459096459096,11400476780
|
|
||||||
219.14695169690575,132.33767019667172,0.5096459096459096,11400486793
|
|
||||||
218.99989787929331,126.41210287443268,0.5089133089133089,11400491781
|
|
||||||
218.8160806072778,121.532223903177,0.503052503052503,11400500805
|
|
||||||
218.59549988085917,117.8142208774584,0.49230769230769234,11400506774
|
|
||||||
218.41168260884365,114.90953101361573,0.46007326007326005,11400515804
|
|
||||||
218.26462879123125,112.93434190620272,0.4073260073260073,11400520780
|
|
||||||
218.15433842802193,111.65627836611195,0.3873015873015873,11400530789
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Stiletto
|
||||||
|
// Copyright (C) 2020 Stiletto Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use clap::{Arg, App};
|
||||||
|
use stiletto::migration::open_stiletto_document;
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let matches = App::new("Stiletto Migration CLI")
|
||||||
|
.version("0.1.0")
|
||||||
|
.author("Stiletto Authors")
|
||||||
|
.about("Migrate a stlt file to the latest version")
|
||||||
|
.arg(Arg::new("INPUT")
|
||||||
|
.about("Sets the input file to use")
|
||||||
|
.required(true)
|
||||||
|
.index(1))
|
||||||
|
.arg(Arg::new("OUTPUT")
|
||||||
|
.about("Sets the output file to use")
|
||||||
|
.required(true)
|
||||||
|
.index(2))
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
let input_path = matches.value_of("INPUT").unwrap();
|
||||||
|
let output_path = matches.value_of("OUTPUT").unwrap();
|
||||||
|
|
||||||
|
let out_file = File::create(&output_path).expect("Cannot create output fle");
|
||||||
|
|
||||||
|
let result = open_stiletto_document(&PathBuf::from(&input_path));
|
||||||
|
if let Ok(document) = result {
|
||||||
|
let snapshot = document.migrate_to_latest();
|
||||||
|
snapshot.to_writer(out_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
303
src/canvas.rs
303
src/canvas.rs
|
|
@ -1,4 +1,37 @@
|
||||||
use druid::kurbo::BezPath;
|
// Stiletto
|
||||||
|
// Copyright (C) 2020 Stiletto Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
use im::{vector, Vector};
|
||||||
|
|
||||||
|
use serde::de::{Deserializer, SeqAccess, Visitor};
|
||||||
|
use serde::ser::Serializer;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, druid::Data)]
|
||||||
|
pub struct Path {
|
||||||
|
pub kurbo_path: druid::kurbo::BezPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(remote = "druid::Color")]
|
||||||
|
enum ColorDef {
|
||||||
|
Rgba32(u32),
|
||||||
|
}
|
||||||
|
|
||||||
struct Interval {
|
struct Interval {
|
||||||
start_pos: f64,
|
start_pos: f64,
|
||||||
|
|
@ -26,8 +59,8 @@ impl Interval {
|
||||||
}
|
}
|
||||||
|
|
||||||
// O(1) complexity
|
// O(1) complexity
|
||||||
// TODO: Implement this in a way that is not so flamboyant
|
// TODO(francesco): Implement this in a way that is not so flamboyant
|
||||||
// TODO: Implement intersection test for bezier curves
|
// TODO(francesco): Implement intersection test for bezier curves
|
||||||
fn segment_intersects_rect(segment: &druid::kurbo::PathSeg, rect: druid::Rect) -> bool {
|
fn segment_intersects_rect(segment: &druid::kurbo::PathSeg, rect: druid::Rect) -> bool {
|
||||||
match segment {
|
match segment {
|
||||||
druid::kurbo::PathSeg::Line(line) => {
|
druid::kurbo::PathSeg::Line(line) => {
|
||||||
|
|
@ -40,7 +73,7 @@ fn segment_intersects_rect(segment: &druid::kurbo::PathSeg, rect: druid::Rect) -
|
||||||
let rect_y_proj = Interval::new_ordered(rect.min_y(), rect.max_y());
|
let rect_y_proj = Interval::new_ordered(rect.min_y(), rect.max_y());
|
||||||
|
|
||||||
line_x_proj.overlaps(&rect_x_proj) && line_y_proj.overlaps(&rect_y_proj)
|
line_x_proj.overlaps(&rect_x_proj) && line_y_proj.overlaps(&rect_y_proj)
|
||||||
},
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
|
|
@ -48,28 +81,22 @@ fn segment_intersects_rect(segment: &druid::kurbo::PathSeg, rect: druid::Rect) -
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, druid::Data, Serialize, Deserialize)]
|
||||||
#[derive(Clone, druid::Data)]
|
|
||||||
pub struct Path {
|
|
||||||
pub kurbo_path: BezPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Path {
|
|
||||||
fn intersects_rect(&self, rect: druid::Rect) -> bool {
|
|
||||||
// The path intersects the rect if any of its segments does
|
|
||||||
self.kurbo_path.segments().any(|segment| segment_intersects_rect(&segment, rect))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, druid::Data)]
|
|
||||||
pub enum CanvasElement {
|
pub enum CanvasElement {
|
||||||
Freehand { path: Path, thickness: f64 },
|
Freehand {
|
||||||
|
path: Path,
|
||||||
|
thickness: f64,
|
||||||
|
#[serde(with = "ColorDef")]
|
||||||
|
stroke_color: druid::Color,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CanvasElement {
|
impl CanvasElement {
|
||||||
pub fn bounding_box(&self) -> druid::Rect {
|
pub fn bounding_box(&self) -> druid::Rect {
|
||||||
match self {
|
match self {
|
||||||
CanvasElement::Freehand { path, thickness } => {
|
CanvasElement::Freehand {
|
||||||
|
path, thickness, ..
|
||||||
|
} => {
|
||||||
use druid::kurbo::Shape;
|
use druid::kurbo::Shape;
|
||||||
path.kurbo_path
|
path.kurbo_path
|
||||||
.bounding_box()
|
.bounding_box()
|
||||||
|
|
@ -77,156 +104,138 @@ impl CanvasElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn draw(&self, ctx: &mut druid::PaintCtx) {
|
|
||||||
|
pub fn intersects_rect(&self, rect: druid::Rect) -> bool {
|
||||||
match self {
|
match self {
|
||||||
CanvasElement::Freehand { path, thickness } => {
|
CanvasElement::Freehand { path, .. } => {
|
||||||
use druid::RenderContext;
|
// TODO(enrico) this doesn't handle thickness
|
||||||
let stroke_color = druid::Color::rgb8(0, 128, 0);
|
path.kurbo_path
|
||||||
ctx.stroke(&path.kurbo_path, &stroke_color, *thickness);
|
.segments()
|
||||||
|
.any(|segment| segment_intersects_rect(&segment, rect))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_path_mut(&mut self) -> Option<&mut Path> {
|
pub fn draw(&self, ctx: &mut druid::PaintCtx) {
|
||||||
match self {
|
match self {
|
||||||
CanvasElement::Freehand { path, .. } => Some(path),
|
CanvasElement::Freehand {
|
||||||
|
path,
|
||||||
|
thickness,
|
||||||
|
stroke_color,
|
||||||
|
} => {
|
||||||
|
use druid::RenderContext;
|
||||||
|
ctx.stroke(&path.kurbo_path, &*stroke_color, *thickness);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A canvas contains all elements to be drawn
|
|
||||||
#[derive(Clone, druid::Data)]
|
#[derive(Clone, druid::Data)]
|
||||||
pub struct Canvas {
|
pub struct Canvas {
|
||||||
pub elements: im::Vector<CanvasElement>,
|
elements: Vector<CanvasElement>,
|
||||||
pub version_id: usize
|
content_size: druid::Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Canvas {
|
impl Canvas {
|
||||||
pub fn new() -> Canvas {
|
pub fn new() -> Self {
|
||||||
Canvas {
|
Canvas {
|
||||||
elements: im::vector![],
|
elements: vector![],
|
||||||
version_id: 0,
|
content_size: druid::Size::new(0.0, 0.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unchanged(&self, other: &Canvas) -> bool {
|
pub fn new_with_elements(elements: Vector<CanvasElement>) -> Self {
|
||||||
self.version_id == other.version_id
|
let mut cv = Canvas::new();
|
||||||
|
for e in elements {
|
||||||
|
cv.add_element(e);
|
||||||
}
|
}
|
||||||
|
cv
|
||||||
// Completely erase all strokes that intersect the rect
|
|
||||||
pub fn erase_strokes(&mut self, rect: druid::Rect) {
|
|
||||||
// TODO: Improve efficiency
|
|
||||||
|
|
||||||
// Linear search through all elements in the canvas
|
|
||||||
// to find those whose bounding box intersects the eraser rect
|
|
||||||
// (note that this doesn't imply that the element itself intersects the eraser rect,
|
|
||||||
// but it helps filtering out elements that are too far away from the eraser)
|
|
||||||
let mut new_elements = im::Vector::new();
|
|
||||||
|
|
||||||
for elem in self.elements.iter() {
|
|
||||||
// Check if the element intersects the eraser rect
|
|
||||||
if elem.bounding_box().intersect(rect).area() > 0.0 {
|
|
||||||
// Check the element type, we only erase Freehand paths
|
|
||||||
match elem {
|
|
||||||
CanvasElement::Freehand {path, ..} => {
|
|
||||||
// Check whether the path intersects the rect,
|
|
||||||
// if so, remove it.
|
|
||||||
if !path.intersects_rect(rect) {
|
|
||||||
new_elements.push_back(elem.clone());
|
|
||||||
} else {
|
|
||||||
self.version_id += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_elements.push_back(elem.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update elements list
|
|
||||||
self.elements = new_elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
// O(n) complexity, where n is the number of segments belonging to paths
|
|
||||||
// that intersect the eraser rectangle
|
|
||||||
// Erase all portions of paths lying in the rect
|
|
||||||
pub fn erase_paths(&mut self, rect: druid::Rect) {
|
|
||||||
// TODO: Improve efficiency
|
|
||||||
|
|
||||||
// Linear search through all elements in the canvas
|
|
||||||
// to find those whose bounding box intersects the eraser rect
|
|
||||||
// (note that this doesn't imply that the element itself intersects the eraser rect,
|
|
||||||
// but it helps filtering out elements that are too far away from the eraser)
|
|
||||||
let mut new_elements = im::Vector::new();
|
|
||||||
|
|
||||||
for elem in self.elements.iter() {
|
|
||||||
// Check if the element intersects the eraser rect
|
|
||||||
if elem.bounding_box().intersect(rect).area() > 0.0 {
|
|
||||||
// Check the element type, we only erase Freehand paths
|
|
||||||
match elem {
|
|
||||||
CanvasElement::Freehand {path, thickness} => {
|
|
||||||
// Remove segments intersecting the eraser
|
|
||||||
// And return the remaining subpaths
|
|
||||||
let (path_changed, new_paths) = Canvas::erase_segments_from_freehand_path(&path, rect);
|
|
||||||
|
|
||||||
if path_changed {
|
|
||||||
// There has been some change, update the canvas
|
|
||||||
self.version_id += 1;
|
|
||||||
for path in new_paths {
|
|
||||||
new_elements.push_back(CanvasElement::Freehand {
|
|
||||||
path: path,
|
|
||||||
thickness: *thickness,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_elements.push_back(elem.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update elements list
|
|
||||||
self.elements = new_elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Erase segments from a path, and returns the new subpaths that are left
|
|
||||||
fn erase_segments_from_freehand_path(path: &Path, rect: druid::Rect) -> (bool, im::Vector<Path>) {
|
|
||||||
|
|
||||||
// Vector of pieces
|
|
||||||
let mut pieces = im::Vector::new();
|
|
||||||
// Current subpath
|
|
||||||
let mut curr_piece = im::Vector::new();
|
|
||||||
|
|
||||||
// Flag indicating whether the segment has been modified
|
|
||||||
let mut erased = false;
|
|
||||||
|
|
||||||
for segment in path.kurbo_path.segments() {
|
|
||||||
if segment_intersects_rect(&segment, rect) {
|
|
||||||
erased = true;
|
|
||||||
// We found a segment to erase, so split the path here
|
|
||||||
// If the path is not empty, add it to the pieces vector
|
|
||||||
if !curr_piece.is_empty() {
|
|
||||||
pieces.push_back(curr_piece);
|
|
||||||
// Recreate accumulator
|
|
||||||
curr_piece = im::Vector::new();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add segment to curr piece
|
|
||||||
curr_piece.push_back(segment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We end up with `pieces`, a vector of non empty paths
|
|
||||||
let paths = pieces.iter().map(|segments| Path {
|
|
||||||
kurbo_path: BezPath::from_path_segments(segments.iter().cloned()),
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
(erased, paths)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_element(&mut self, element: CanvasElement) {
|
pub fn add_element(&mut self, element: CanvasElement) {
|
||||||
self.version_id += 1;
|
self.content_size = self
|
||||||
|
.content_size
|
||||||
|
.to_rect()
|
||||||
|
.union(element.bounding_box())
|
||||||
|
.size();
|
||||||
self.elements.push_back(element);
|
self.elements.push_back(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn elements(&self) -> &Vector<CanvasElement> {
|
||||||
|
&self.elements
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find all CanvasElement that intersect with rect
|
||||||
|
pub fn find_intersections(&self, rect: druid::Rect) -> Vec<usize> {
|
||||||
|
let mut found_elements = Vec::<usize>::new();
|
||||||
|
|
||||||
|
for (i, elem) in self.elements.iter().enumerate() {
|
||||||
|
// Check if the element intersects the eraser rect
|
||||||
|
if elem.bounding_box().intersect(rect).area() > 0.0 {
|
||||||
|
if elem.intersects_rect(rect) {
|
||||||
|
found_elements.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found_elements
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content_size(&self) -> druid::Size {
|
||||||
|
self.content_size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Path {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
use druid::kurbo::PathEl;
|
||||||
|
|
||||||
|
serializer.collect_seq(self.kurbo_path.iter().filter_map(|path_el| match path_el {
|
||||||
|
PathEl::MoveTo(pt) => Some(Into::<(f64, f64)>::into(pt)),
|
||||||
|
PathEl::LineTo(pt) => Some(Into::<(f64, f64)>::into(pt)),
|
||||||
|
_ => None,
|
||||||
|
}).collect::<Vec<(f64, f64)>>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Path {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct PathVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for PathVisitor {
|
||||||
|
type Value = Path;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(formatter, "A sequence of 2D points")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
use druid::kurbo::BezPath;
|
||||||
|
|
||||||
|
let mut kurbo_path = BezPath::new();
|
||||||
|
let mut first_element = true;
|
||||||
|
while let Some(point) = seq.next_element::<(f64, f64)>()? {
|
||||||
|
if first_element {
|
||||||
|
kurbo_path.move_to(point);
|
||||||
|
first_element = false;
|
||||||
|
} else {
|
||||||
|
kurbo_path.line_to(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Path { kurbo_path })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_seq(PathVisitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
use crate::canvas::*;
|
|
||||||
use crate::versioned_canvas::VersionedCanvas;
|
|
||||||
|
|
||||||
use druid::kurbo::BezPath;
|
|
||||||
use druid::{Data, Event, Rect};
|
|
||||||
|
|
||||||
// Tools that can be used to interact with the canvas
|
|
||||||
#[derive(Clone, Data)]
|
|
||||||
pub enum CanvasTool {
|
|
||||||
Pen {
|
|
||||||
current_element: Option<CanvasElement>,
|
|
||||||
},
|
|
||||||
Eraser {
|
|
||||||
eraser_rect: Option<Rect>,
|
|
||||||
},
|
|
||||||
|
|
||||||
StrokeEraser {
|
|
||||||
eraser_rect: Option<Rect>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CanvasTool {
|
|
||||||
pub fn new_pen() -> CanvasTool {
|
|
||||||
CanvasTool::Pen {
|
|
||||||
current_element: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_eraser() -> CanvasTool {
|
|
||||||
CanvasTool::Eraser { eraser_rect: None }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_stroke_eraser() -> CanvasTool {
|
|
||||||
CanvasTool::StrokeEraser { eraser_rect: None }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_event(&mut self, event: &Event, canvas: &mut VersionedCanvas) {
|
|
||||||
match self {
|
|
||||||
CanvasTool::Pen { current_element } => {
|
|
||||||
CanvasTool::pen_handle_event(current_element, event, canvas)
|
|
||||||
}
|
|
||||||
CanvasTool::Eraser { eraser_rect } => {
|
|
||||||
CanvasTool::eraser_handle_event(eraser_rect, event, canvas, false)
|
|
||||||
}
|
|
||||||
CanvasTool::StrokeEraser { eraser_rect } => {
|
|
||||||
CanvasTool::eraser_handle_event(eraser_rect, event, canvas, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pen_handle_event(
|
|
||||||
current_element: &mut Option<CanvasElement>,
|
|
||||||
event: &Event,
|
|
||||||
canvas: &mut VersionedCanvas,
|
|
||||||
) {
|
|
||||||
match event {
|
|
||||||
Event::MouseDown(mouse_event) => {
|
|
||||||
let mut kurbo_path = BezPath::new();
|
|
||||||
kurbo_path.move_to((mouse_event.pos.x, mouse_event.pos.y));
|
|
||||||
*current_element = Some(CanvasElement::Freehand {
|
|
||||||
path: Path { kurbo_path },
|
|
||||||
thickness: 2.0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Event::MouseMove(mouse_event) => {
|
|
||||||
if current_element.is_some() {
|
|
||||||
if let Some(current_element) = current_element.as_mut() {
|
|
||||||
current_element
|
|
||||||
.get_path_mut()
|
|
||||||
.unwrap()
|
|
||||||
.kurbo_path
|
|
||||||
.line_to((mouse_event.pos.x, mouse_event.pos.y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::MouseUp(_) => {
|
|
||||||
if current_element.is_some() {
|
|
||||||
if let Some(current_element) = current_element.take() {
|
|
||||||
canvas.update(move |canvas: &mut Canvas| {
|
|
||||||
canvas.add_element(current_element)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eraser_handle_event(
|
|
||||||
eraser_rect: &mut Option<Rect>,
|
|
||||||
event: &Event,
|
|
||||||
canvas: &mut VersionedCanvas,
|
|
||||||
is_stroke_eraser: bool
|
|
||||||
) {
|
|
||||||
match event {
|
|
||||||
Event::MouseDown(mouse_event) => {
|
|
||||||
let rect = druid::Rect::from_center_size(mouse_event.pos, druid::Size::new(10.0, 10.0));
|
|
||||||
*eraser_rect = Some(rect);
|
|
||||||
// Create a new undo version each time the mouse is down
|
|
||||||
canvas.update(|canvas: &mut Canvas| {
|
|
||||||
if is_stroke_eraser {
|
|
||||||
canvas.erase_strokes(rect)
|
|
||||||
} else {
|
|
||||||
canvas.erase_paths(rect)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Event::MouseMove(mouse_event) => {
|
|
||||||
if eraser_rect.is_some() {
|
|
||||||
let rect = druid::Rect::from_center_size(
|
|
||||||
mouse_event.pos,
|
|
||||||
druid::Size::new(10.0, 10.0),
|
|
||||||
);
|
|
||||||
*eraser_rect = Some(rect);
|
|
||||||
// We don't want too many levels of undoing
|
|
||||||
// So we make irreversible changes as long as
|
|
||||||
// the mouse is pressed.
|
|
||||||
canvas.irreversible_update(|canvas: &mut Canvas| {
|
|
||||||
if is_stroke_eraser {
|
|
||||||
canvas.erase_strokes(rect)
|
|
||||||
} else {
|
|
||||||
canvas.erase_paths(rect)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::MouseUp(_) => {
|
|
||||||
*eraser_rect = None;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Stiletto
|
||||||
|
// Copyright (C) 2020 Stiletto Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::canvas::Canvas;
|
||||||
|
use im::Vector;
|
||||||
|
|
||||||
|
#[derive(Clone, druid::Data)]
|
||||||
|
pub struct VersionedCanvas {
|
||||||
|
// We internally guarantee that this vector
|
||||||
|
// is never empty
|
||||||
|
versions: Vector<Canvas>,
|
||||||
|
curr_version: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionedCanvas {
|
||||||
|
pub fn new(canvas: Canvas) -> VersionedCanvas {
|
||||||
|
VersionedCanvas {
|
||||||
|
versions: im::vector![canvas],
|
||||||
|
curr_version: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current canvas version
|
||||||
|
pub fn get(&self) -> &Canvas {
|
||||||
|
self.versions.get(self.curr_version).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_newer_versions(&self) -> bool {
|
||||||
|
self.curr_version + 1 < self.versions.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_older_versions(&self) -> bool {
|
||||||
|
self.curr_version > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn undo(&mut self) {
|
||||||
|
if self.has_older_versions() {
|
||||||
|
self.curr_version -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redo(&mut self) {
|
||||||
|
if self.has_newer_versions() {
|
||||||
|
self.curr_version += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, update_fn: impl FnOnce(&mut Canvas)) {
|
||||||
|
// Make a new copy of the current canvas version,
|
||||||
|
// so that we can safely modify it without losing
|
||||||
|
// the previous canvas version
|
||||||
|
let mut new_version = self.get().clone();
|
||||||
|
update_fn(&mut new_version);
|
||||||
|
|
||||||
|
// This is a linear history,
|
||||||
|
// so we first check if there are newer versions, if so
|
||||||
|
// this means we are in the past, so a change in the past destroys the future
|
||||||
|
// and creates a new future.
|
||||||
|
if self.has_newer_versions() {
|
||||||
|
self.versions = self.versions.take(self.curr_version + 1);
|
||||||
|
}
|
||||||
|
self.versions.push_back(new_version);
|
||||||
|
self.curr_version += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do inplace update, which will be irreversible
|
||||||
|
pub fn irreversible_update(&mut self, update_fn: impl FnOnce(&mut Canvas)) {
|
||||||
|
// Do the update directly on the current canvas version
|
||||||
|
update_fn(self.versions.back_mut().unwrap());
|
||||||
|
|
||||||
|
// This is a linear history,
|
||||||
|
// so we first check if there are newer versions, if so
|
||||||
|
// this means we are in the past, so a change in the past destroys the future
|
||||||
|
// and creates a new future.
|
||||||
|
if self.has_newer_versions() {
|
||||||
|
self.versions = self.versions.take(self.curr_version + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/lib.rs
49
src/lib.rs
|
|
@ -1,3 +1,48 @@
|
||||||
|
// Stiletto
|
||||||
|
// Copyright (C) 2020 Stiletto Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
pub mod canvas;
|
pub mod canvas;
|
||||||
pub mod canvas_tools;
|
pub mod history;
|
||||||
pub mod versioned_canvas;
|
pub mod tool;
|
||||||
|
pub mod widget;
|
||||||
|
pub mod migration;
|
||||||
|
|
||||||
|
pub mod commands {
|
||||||
|
use druid::Selector;
|
||||||
|
|
||||||
|
pub const UPDATE_TOOL: Selector = Selector::new("stiletto_update_tool");
|
||||||
|
pub const PUSH_ERASER: Selector<()> = Selector::new("push_eraser_context");
|
||||||
|
pub const POP_ERASER: Selector<()> = Selector::new("pop_eraser_context");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const STILETTO_FORMAT_MAJOR: u16 = 0;
|
||||||
|
pub const STILETTO_FORMAT_MINOR: u16 = 2;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DocumentSnapshot {
|
||||||
|
pub format_version_major: u16,
|
||||||
|
pub format_version_minor: u16,
|
||||||
|
pub canvas_elements: Vec<canvas::CanvasElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocumentSnapshot {
|
||||||
|
pub fn to_writer<W: std::io::Write>(&self, writer: W) -> Result<(), serde_bare::Error> {
|
||||||
|
serde_bare::to_writer(writer, &self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
436
src/main.rs
436
src/main.rs
|
|
@ -14,216 +14,282 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use log::{info, warn};
|
||||||
|
|
||||||
|
use druid::commands;
|
||||||
|
use druid::im::vector;
|
||||||
use druid::widget::prelude::*;
|
use druid::widget::prelude::*;
|
||||||
|
use druid::widget::{
|
||||||
|
Align, Button, Controller, CrossAxisAlignment, Flex, List, SizedBox, WidgetExt,
|
||||||
|
};
|
||||||
use druid::{
|
use druid::{
|
||||||
AppLauncher, Color, Data, Event, Lens, LensExt, LocalizedString, WidgetExt, WindowDesc,
|
AppDelegate, AppLauncher, Color, Command, Data, DelegateCtx, Env, FileDialogOptions, FileSpec,
|
||||||
|
Handled, Lens, Target, WindowDesc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use stiletto::canvas::Canvas;
|
use im::Vector;
|
||||||
use stiletto::canvas_tools::CanvasTool;
|
|
||||||
use stiletto::versioned_canvas::VersionedCanvas;
|
use stiletto::tool::CanvasToolParams;
|
||||||
|
use stiletto::widget::tool_ctx::{CanvasToolCtx};
|
||||||
|
use stiletto::widget::{build_simple_tool_widget, CanvasState, CanvasToolIconState, CanvasWidget};
|
||||||
|
use stiletto::DocumentSnapshot;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let window = WindowDesc::new(build_ui)
|
||||||
|
.window_size((1024.0, 1400.0))
|
||||||
|
.title(|data: &StilettoState, _env: &Env| {
|
||||||
|
let doc_name = if let Some(path) = &data.current_file_path {
|
||||||
|
String::from(path.to_string_lossy())
|
||||||
|
} else {
|
||||||
|
String::from("New Document")
|
||||||
|
};
|
||||||
|
format!("Stiletto - {}", doc_name)
|
||||||
|
});
|
||||||
|
|
||||||
|
let default_pen_params = CanvasToolParams::Pen {
|
||||||
|
thickness: 2.0,
|
||||||
|
color: Color::rgb(0.0, 0.0, 0.0),
|
||||||
|
};
|
||||||
|
let default_pen_params_2 = CanvasToolParams::Pen {
|
||||||
|
thickness: 2.0,
|
||||||
|
color: Color::rgb(255.0, 0.0, 0.0),
|
||||||
|
};
|
||||||
|
let canvas_data = StilettoState {
|
||||||
|
canvas: CanvasState::new(CanvasToolCtx::new(default_pen_params.clone())),
|
||||||
|
tool_icons: vector![
|
||||||
|
CanvasToolIconState {
|
||||||
|
tool_params: default_pen_params,
|
||||||
|
selected: true,
|
||||||
|
},
|
||||||
|
CanvasToolIconState {
|
||||||
|
tool_params: default_pen_params_2,
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
CanvasToolIconState {
|
||||||
|
tool_params: CanvasToolParams::Eraser,
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
current_tool: 0,
|
||||||
|
eraser_tool_id: 2,
|
||||||
|
current_file_path: None,
|
||||||
|
};
|
||||||
|
AppLauncher::with_window(window)
|
||||||
|
.use_simple_logger()
|
||||||
|
.delegate(Delegate)
|
||||||
|
.launch(canvas_data)
|
||||||
|
.expect("launch failed");
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Data, Lens)]
|
#[derive(Clone, Data, Lens)]
|
||||||
struct CanvasData {
|
struct StilettoState {
|
||||||
#[lens(name = "current_tool_lens")]
|
canvas: CanvasState,
|
||||||
current_tool: CanvasTool,
|
tool_icons: Vector<CanvasToolIconState>,
|
||||||
canvas: VersionedCanvas,
|
current_tool: usize,
|
||||||
|
eraser_tool_id: usize,
|
||||||
|
#[data(same_fn = "PartialEq::eq")]
|
||||||
|
current_file_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CanvasData {
|
impl StilettoState {
|
||||||
fn perform_undo(&mut self) {
|
pub fn set_tool(&mut self, new_tool: usize) {
|
||||||
self.canvas.undo();
|
let old_tool = self.current_tool;
|
||||||
}
|
info!("Changing active tool to: tool #{}", new_tool);
|
||||||
|
self.tool_icons.get_mut(old_tool).unwrap().selected = false;
|
||||||
fn perform_redo(&mut self) {
|
self.tool_icons.get_mut(new_tool).unwrap().selected = true;
|
||||||
self.canvas.redo();
|
self.current_tool = new_tool;
|
||||||
}
|
self.canvas.set_tool_ctx(CanvasToolCtx::new(
|
||||||
|
self.tool_icons.get(new_tool).unwrap().tool_params.clone(),
|
||||||
fn handle_tool_event(&mut self, event: &Event) {
|
));
|
||||||
self.current_tool.handle_event(event, &mut self.canvas);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CanvasWidget;
|
fn build_ui() -> impl Widget<StilettoState> {
|
||||||
|
let history_buttons = Flex::row()
|
||||||
|
.cross_axis_alignment(CrossAxisAlignment::Center)
|
||||||
|
.with_child(Button::new("Undo").on_click(
|
||||||
|
|_ctx: &mut EventCtx, data: &mut StilettoState, _env: &Env| data.canvas.perform_undo(),
|
||||||
|
))
|
||||||
|
.with_child(Button::new("Redo").on_click(
|
||||||
|
|_ctx: &mut EventCtx, data: &mut StilettoState, _env: &Env| data.canvas.perform_redo(),
|
||||||
|
));
|
||||||
|
|
||||||
impl Widget<CanvasData> for CanvasWidget {
|
let stlt = FileSpec::new("Stiletto notebook", &["stlt"]);
|
||||||
fn event(&mut self, _ctx: &mut EventCtx, event: &Event, data: &mut CanvasData, _env: &Env) {
|
let save_dialog_options = FileDialogOptions::new()
|
||||||
data.handle_tool_event(event);
|
.allowed_types(vec![stlt])
|
||||||
}
|
.default_type(stlt);
|
||||||
|
let save_dialog_options_clone = save_dialog_options.clone();
|
||||||
|
let open_dialog_options = save_dialog_options.clone();
|
||||||
|
|
||||||
fn lifecycle(
|
let save_buttons = Flex::row()
|
||||||
&mut self,
|
.cross_axis_alignment(CrossAxisAlignment::Center)
|
||||||
_ctx: &mut LifeCycleCtx,
|
.with_child(
|
||||||
_event: &LifeCycle,
|
Button::new("Save").on_click(move |ctx, data: &mut StilettoState, _| {
|
||||||
_data: &CanvasData,
|
if data.current_file_path.is_some() {
|
||||||
_env: &Env,
|
ctx.submit_command(Command::new(commands::SAVE_FILE, (), Target::Auto));
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut UpdateCtx,
|
|
||||||
old_data: &CanvasData,
|
|
||||||
data: &CanvasData,
|
|
||||||
_env: &Env,
|
|
||||||
) {
|
|
||||||
// TODO: Polish a bit this logic
|
|
||||||
match (&old_data.current_tool, &data.current_tool) {
|
|
||||||
(
|
|
||||||
CanvasTool::Pen {
|
|
||||||
current_element: old_element,
|
|
||||||
},
|
|
||||||
CanvasTool::Pen {
|
|
||||||
current_element: new_element,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
// the current_element is moved to the elements array, no need to repaint
|
|
||||||
if old_element.is_some() && !new_element.is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_element.is_some() {
|
|
||||||
if let Some(e) = new_element.as_ref() {
|
|
||||||
ctx.request_paint_rect(e.bounding_box());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ctx.request_paint();
|
ctx.submit_command(Command::new(
|
||||||
}
|
druid::commands::SHOW_SAVE_PANEL,
|
||||||
}
|
save_dialog_options_clone.clone(),
|
||||||
(CanvasTool::Eraser{..}, CanvasTool::Eraser{ eraser_rect }) => {
|
Target::Auto,
|
||||||
if let Some(rect) = eraser_rect {
|
))
|
||||||
ctx.request_paint_rect(*rect);
|
|
||||||
} else {
|
|
||||||
ctx.request_paint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(CanvasTool::StrokeEraser{..}, CanvasTool::StrokeEraser{..}) => {
|
|
||||||
ctx.request_paint();
|
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.with_child(Button::new("Save As").on_click(move |ctx, _, _| {
|
||||||
|
ctx.submit_command(Command::new(
|
||||||
|
druid::commands::SHOW_SAVE_PANEL,
|
||||||
|
save_dialog_options.clone(),
|
||||||
|
Target::Auto,
|
||||||
|
))
|
||||||
|
}))
|
||||||
|
.with_child(Button::new("Open").on_click(move |ctx, _, _| {
|
||||||
|
ctx.submit_command(Command::new(
|
||||||
|
druid::commands::SHOW_OPEN_PANEL,
|
||||||
|
open_dialog_options.clone(),
|
||||||
|
Target::Auto,
|
||||||
|
))
|
||||||
|
}));
|
||||||
|
|
||||||
_ => {
|
let tool_buttons = Flex::row()
|
||||||
// we just changed the canvas tool, there is no need to repaint
|
.cross_axis_alignment(CrossAxisAlignment::Center)
|
||||||
return;
|
.with_flex_child(
|
||||||
}
|
List::new(|| {
|
||||||
}
|
build_simple_tool_widget(30.0, 30.0, 5.0)
|
||||||
}
|
.padding((8.0, 0.0))
|
||||||
|
.on_click(|ctx, tool_state, _| {
|
||||||
fn layout(
|
tool_state.selected = true;
|
||||||
&mut self,
|
ctx.submit_notification(stiletto::commands::UPDATE_TOOL);
|
||||||
_layout_ctx: &mut LayoutCtx,
|
})
|
||||||
bc: &BoxConstraints,
|
})
|
||||||
_data: &CanvasData,
|
.horizontal()
|
||||||
_env: &Env,
|
.lens(StilettoState::tool_icons),
|
||||||
) -> Size {
|
1.0,
|
||||||
// BoxConstraints are passed by the parent widget.
|
);
|
||||||
// This method can return any Size within those constraints:
|
|
||||||
// bc.constrain(my_size)
|
|
||||||
//
|
|
||||||
// To check if a dimension is infinite or not (e.g. scrolling):
|
|
||||||
// bc.is_width_bounded() / bc.is_height_bounded()
|
|
||||||
bc.max()
|
|
||||||
}
|
|
||||||
|
|
||||||
// The paint method gets called last, after an event flow.
|
|
||||||
// It goes event -> update -> layout -> paint, and each method can influence the next.
|
|
||||||
// Basically, anything that changes the appearance of a widget causes a paint.
|
|
||||||
fn paint(&mut self, ctx: &mut PaintCtx, data: &CanvasData, _env: &Env) {
|
|
||||||
// (ctx.size() returns the size of the layout rect we're painting in)
|
|
||||||
let size = ctx.size();
|
|
||||||
let rect = size.to_rect();
|
|
||||||
// Note: ctx also has a `clear` method, but that clears the whole context,
|
|
||||||
// and we only want to clear this widget's area.
|
|
||||||
ctx.fill(rect, &Color::WHITE);
|
|
||||||
|
|
||||||
for element in data.canvas.get().elements.iter() {
|
|
||||||
element.draw(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
match &data.current_tool {
|
|
||||||
CanvasTool::Pen { current_element } => {
|
|
||||||
if let Some(element) = ¤t_element {
|
|
||||||
element.draw(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enum containing an entry for each canvas tool
|
|
||||||
// it is used to implement the radio group for selecting the tool
|
|
||||||
#[derive(Clone, Data, PartialEq)]
|
|
||||||
enum ToolType {
|
|
||||||
Pen,
|
|
||||||
Eraser,
|
|
||||||
StrokeEraser,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a CanvasTool of a given type
|
|
||||||
fn update_canvas_tool_from_type(tool: &mut CanvasTool, new_type: ToolType) {
|
|
||||||
match new_type {
|
|
||||||
ToolType::Pen => *tool = CanvasTool::new_pen(),
|
|
||||||
ToolType::Eraser => *tool = CanvasTool::new_eraser(),
|
|
||||||
ToolType::StrokeEraser => *tool = CanvasTool::new_stroke_eraser(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a ToolType from a CanvasTool
|
|
||||||
fn canvas_tool_type(tool: &CanvasTool) -> ToolType {
|
|
||||||
match tool {
|
|
||||||
CanvasTool::Pen { .. } => ToolType::Pen,
|
|
||||||
CanvasTool::Eraser { .. } => ToolType::Eraser,
|
|
||||||
CanvasTool::StrokeEraser { .. } => ToolType::StrokeEraser,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_ui() -> impl Widget<CanvasData> {
|
|
||||||
use druid::widget::{Align, Button, CrossAxisAlignment, Flex, Radio, SizedBox};
|
|
||||||
|
|
||||||
|
|
||||||
let toolbar = Flex::row()
|
let toolbar = Flex::row()
|
||||||
.cross_axis_alignment(CrossAxisAlignment::Center)
|
.cross_axis_alignment(CrossAxisAlignment::Center)
|
||||||
.with_spacer(30.0)
|
.with_spacer(30.0)
|
||||||
.with_child(
|
.with_flex_child(Align::left(history_buttons), 1.0)
|
||||||
Button::new("Undo").on_click(|_ctx, data: &mut CanvasData, _env| data.perform_undo()),
|
.with_spacer(10.0)
|
||||||
)
|
.with_flex_child(Align::left(tool_buttons), 2.0)
|
||||||
.with_child(
|
.with_spacer(20.0)
|
||||||
Button::new("Redo").on_click(|_ctx, data: &mut CanvasData, _env| data.perform_redo()),
|
.with_flex_child(Align::right(save_buttons), 1.0)
|
||||||
)
|
.with_spacer(30.0);
|
||||||
.with_child(
|
|
||||||
Radio::new("Pen", ToolType::Pen).lens(CanvasData::current_tool_lens.map(canvas_tool_type, update_canvas_tool_from_type))
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
Radio::new("Eraser", ToolType::Eraser).lens(CanvasData::current_tool_lens.map(canvas_tool_type, update_canvas_tool_from_type))
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
Radio::new("Stroke Eraser", ToolType::StrokeEraser).lens(CanvasData::current_tool_lens.map(canvas_tool_type, update_canvas_tool_from_type))
|
|
||||||
);
|
|
||||||
// ^ We use a lens to make the radiogroup act only on the tool portion of data.
|
|
||||||
// Actually we must also perform a conversion, between ToolType and CanvasTool,
|
|
||||||
// that's why we perform a map on the lens.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.cross_axis_alignment(CrossAxisAlignment::Center)
|
.cross_axis_alignment(CrossAxisAlignment::Center)
|
||||||
.must_fill_main_axis(true)
|
.must_fill_main_axis(true)
|
||||||
.with_child(SizedBox::new(Align::left(toolbar)).height(50.0))
|
.with_child(SizedBox::new(Align::left(toolbar)).height(50.0))
|
||||||
.with_flex_child(CanvasWidget {}, 1.0)
|
.with_flex_child((CanvasWidget).lens(StilettoState::canvas), 1.0)
|
||||||
|
.controller(ToolSwitcher::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
struct ToolSwitcher {
|
||||||
let window = WindowDesc::new(build_ui)
|
saved_tool: usize,
|
||||||
.window_size((1024.0, 1400.0))
|
}
|
||||||
.title(
|
|
||||||
LocalizedString::new("custom-widget-demo-window-title").with_placeholder("Stiletto"),
|
impl ToolSwitcher {
|
||||||
);
|
pub fn new() -> Self {
|
||||||
let canvas_data = CanvasData {
|
ToolSwitcher { saved_tool: 0 }
|
||||||
current_tool: CanvasTool::new_pen(),
|
}
|
||||||
canvas: VersionedCanvas::new(Canvas::new()),
|
}
|
||||||
};
|
|
||||||
AppLauncher::with_window(window)
|
impl<W: Widget<StilettoState>> Controller<StilettoState, W> for ToolSwitcher {
|
||||||
.use_simple_logger()
|
fn event(
|
||||||
.launch(canvas_data)
|
&mut self,
|
||||||
.expect("launch failed");
|
child: &mut W,
|
||||||
|
ctx: &mut EventCtx,
|
||||||
|
event: &Event,
|
||||||
|
data: &mut StilettoState,
|
||||||
|
env: &Env,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
Event::Notification(cmd) => {
|
||||||
|
let new_tool = if cmd.get(stiletto::commands::PUSH_ERASER).is_some() {
|
||||||
|
ctx.set_handled();
|
||||||
|
self.saved_tool = data.current_tool;
|
||||||
|
Some(data.eraser_tool_id)
|
||||||
|
} else if cmd.get(stiletto::commands::POP_ERASER).is_some() {
|
||||||
|
ctx.set_handled();
|
||||||
|
Some(self.saved_tool)
|
||||||
|
} else if cmd.get(stiletto::commands::UPDATE_TOOL).is_some() {
|
||||||
|
ctx.set_handled();
|
||||||
|
let old_tool = data.current_tool;
|
||||||
|
data.tool_icons
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.position(|(pos, x)| pos != old_tool && x.selected)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(new_tool) = new_tool {
|
||||||
|
data.set_tool(new_tool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
child.event(ctx, event, data, env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Delegate;
|
||||||
|
|
||||||
|
impl AppDelegate<StilettoState> for Delegate {
|
||||||
|
fn command(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut DelegateCtx,
|
||||||
|
_target: Target,
|
||||||
|
cmd: &Command,
|
||||||
|
data: &mut StilettoState,
|
||||||
|
_env: &Env,
|
||||||
|
) -> Handled {
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
if cmd.get(commands::SAVE_FILE_AS).is_some() || cmd.get(commands::SAVE_FILE).is_some() {
|
||||||
|
let mut path_buf = cmd
|
||||||
|
.get(commands::SAVE_FILE_AS)
|
||||||
|
.map(|file_info| file_info.path().to_path_buf())
|
||||||
|
.or_else(|| data.current_file_path.as_ref().cloned())
|
||||||
|
.unwrap();
|
||||||
|
path_buf.set_extension("stlt");
|
||||||
|
|
||||||
|
let res_file = File::create(&path_buf);
|
||||||
|
if let Ok(f) = res_file {
|
||||||
|
let write_res =
|
||||||
|
data.canvas.get_document_snapshot().to_writer(f);
|
||||||
|
if write_res.is_err() {
|
||||||
|
warn!("Error while saving: {:?}", write_res.err());
|
||||||
|
} else {
|
||||||
|
info!("Written to file: {}", &path_buf.display());
|
||||||
|
data.current_file_path = Some(path_buf);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Cannot create file: {:?}", res_file.err());
|
||||||
|
}
|
||||||
|
return Handled::Yes;
|
||||||
|
}
|
||||||
|
if let Some(file_info) = cmd.get(commands::OPEN_FILE) {
|
||||||
|
if let Ok(f) = File::open(file_info.path()) {
|
||||||
|
let res_snapshot: Result<DocumentSnapshot, serde_bare::Error> =
|
||||||
|
serde_bare::from_reader(f);
|
||||||
|
if let Ok(document_snapshot) = res_snapshot {
|
||||||
|
data.canvas.set_from_snapshot(document_snapshot);
|
||||||
|
data.current_file_path = Some(file_info.path().to_path_buf());
|
||||||
|
info!("Loaded file {}", file_info.path().display());
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"Error while loading {}: {:?}",
|
||||||
|
file_info.path().display(),
|
||||||
|
res_snapshot.err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Handled::Yes;
|
||||||
|
}
|
||||||
|
Handled::No
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
// Stiletto
|
||||||
|
// Copyright (C) 2020 Stiletto Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::{DocumentSnapshot, STILETTO_FORMAT_MAJOR, STILETTO_FORMAT_MINOR};
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MigrationError {
|
||||||
|
SerdeJsonError(serde_json::Error),
|
||||||
|
SerdeBareError(serde_bare::Error),
|
||||||
|
IoError(io::Error),
|
||||||
|
|
||||||
|
UnexpectedVersion(u16, u16, u16, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum StilettoDocument {
|
||||||
|
DocumentSnapshot0_1(DocumentSnapshot),
|
||||||
|
DocumentSnapshot0_2(DocumentSnapshot),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StilettoDocument {
|
||||||
|
pub fn version(&self) -> (u16, u16) {
|
||||||
|
match &self {
|
||||||
|
StilettoDocument::DocumentSnapshot0_1(_) => (0, 1),
|
||||||
|
StilettoDocument::DocumentSnapshot0_2(_) => (0, 2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn migrate_to_next(self) -> StilettoDocument {
|
||||||
|
match self {
|
||||||
|
StilettoDocument::DocumentSnapshot0_1(snapshot) => StilettoDocument::DocumentSnapshot0_2(snapshot) ,
|
||||||
|
StilettoDocument::DocumentSnapshot0_2(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn migrate_to_latest(mut self) -> DocumentSnapshot {
|
||||||
|
while self.version() != (STILETTO_FORMAT_MAJOR, STILETTO_FORMAT_MINOR) {
|
||||||
|
self = self.migrate_to_next()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(matches!(self, StilettoDocument::DocumentSnapshot0_2(_)));
|
||||||
|
match self {
|
||||||
|
StilettoDocument::DocumentSnapshot0_2(snapshot) => snapshot,
|
||||||
|
_ => panic!("Wrong Document Snapshot Version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl fmt::Display for MigrationError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match &self {
|
||||||
|
MigrationError::SerdeJsonError(e) => e.fmt(f),
|
||||||
|
MigrationError::SerdeBareError(e) => e.fmt(f),
|
||||||
|
MigrationError::IoError(e) => e.fmt(f),
|
||||||
|
MigrationError::UnexpectedVersion(major, minor, e_major, e_minor) => {
|
||||||
|
write!(
|
||||||
|
f, "Unexpected version ({}, {}): was expecting ({}, {})",
|
||||||
|
major, minor, e_major, e_minor
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for MigrationError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
MigrationError::SerdeJsonError(e) => e.source(),
|
||||||
|
MigrationError::SerdeBareError(e) => e.source(),
|
||||||
|
MigrationError::IoError(e) => e.source(),
|
||||||
|
MigrationError::UnexpectedVersion(..) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for MigrationError {
|
||||||
|
fn from(error: io::Error) -> Self {
|
||||||
|
MigrationError::IoError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for MigrationError {
|
||||||
|
fn from(error: serde_json::Error) -> Self {
|
||||||
|
MigrationError::SerdeJsonError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_bare::Error> for MigrationError {
|
||||||
|
fn from(error: serde_bare::Error) -> Self {
|
||||||
|
MigrationError::SerdeBareError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_0_1(path: &PathBuf) -> Result<StilettoDocument, MigrationError> {
|
||||||
|
let f = File::open(path)?;
|
||||||
|
let document_snapshot: DocumentSnapshot = serde_json::from_reader(f)?;
|
||||||
|
if document_snapshot.format_version_major != 0 ||
|
||||||
|
document_snapshot.format_version_minor != 1 {
|
||||||
|
Err(MigrationError::UnexpectedVersion(
|
||||||
|
document_snapshot.format_version_major,
|
||||||
|
document_snapshot.format_version_minor,
|
||||||
|
0, 1
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(StilettoDocument::DocumentSnapshot0_1(document_snapshot))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_0_2(path: &PathBuf) -> Result<StilettoDocument, MigrationError> {
|
||||||
|
let f = File::open(path)?;
|
||||||
|
let document_snapshot: DocumentSnapshot = serde_bare::from_reader(f)?;
|
||||||
|
Ok(StilettoDocument::DocumentSnapshot0_2(document_snapshot))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_stiletto_document(path: &PathBuf) -> Result<StilettoDocument, MigrationError> {
|
||||||
|
if let Ok(res) = open_0_1(path) {
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
open_0_2(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Stiletto
|
||||||
|
// Copyright (C) 2020 Stiletto Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use druid::{Color, Data};
|
||||||
|
|
||||||
|
#[derive(Clone, Data)]
|
||||||
|
pub enum CanvasToolParams {
|
||||||
|
Pen { thickness: f64, color: Color },
|
||||||
|
Eraser,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum CanvasToolType {
|
||||||
|
Pen,
|
||||||
|
Eraser,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
use crate::canvas::Canvas;
|
|
||||||
|
|
||||||
#[derive(Clone, druid::Data)]
|
|
||||||
pub struct VersionedCanvas {
|
|
||||||
// We internally guarantee that this vector
|
|
||||||
// is never empty
|
|
||||||
versions: im::Vector<Canvas>,
|
|
||||||
curr_version: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VersionedCanvas {
|
|
||||||
pub fn new(canvas: Canvas) -> VersionedCanvas {
|
|
||||||
VersionedCanvas {
|
|
||||||
versions: im::vector![canvas],
|
|
||||||
curr_version: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current canvas version
|
|
||||||
pub fn get(&self) -> &Canvas {
|
|
||||||
self.versions.get(self.curr_version).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_newer_versions(&self) -> bool {
|
|
||||||
self.curr_version + 1 < self.versions.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_older_versions(&self) -> bool {
|
|
||||||
self.curr_version > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn version(&self) -> usize {
|
|
||||||
self.curr_version
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn undo(&mut self) {
|
|
||||||
if self.has_older_versions() {
|
|
||||||
self.curr_version = self.curr_version - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redo(&mut self) {
|
|
||||||
if self.has_newer_versions() {
|
|
||||||
self.curr_version = self.curr_version + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Right now the update function internally checks
|
|
||||||
// whether the canvas has been changed by leveraging the
|
|
||||||
// Copy on Write semantics of im::Vector.
|
|
||||||
// Is this a good solution? Does this work correctly? THINK ABOUT THIS
|
|
||||||
pub fn update(&mut self, update_fn: impl FnOnce(&mut Canvas)) {
|
|
||||||
let mut new_version = self.get().clone();
|
|
||||||
update_fn(&mut new_version);
|
|
||||||
|
|
||||||
// Only push new version if there has been an actual change in the vector
|
|
||||||
if !new_version.unchanged(&self.get()) {
|
|
||||||
// This is a linear history,
|
|
||||||
// so we first check if there are newer versions, if so
|
|
||||||
// this means we are in the past, so a change in the past destroys the future
|
|
||||||
// and creates a new future.
|
|
||||||
if self.has_newer_versions() {
|
|
||||||
self.versions = self.versions.take(self.curr_version + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.versions.push_back(new_version);
|
|
||||||
self.curr_version = self.curr_version + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do inplace update, which will be irreversible
|
|
||||||
pub fn irreversible_update(&mut self, update_fn: impl FnOnce(&mut Canvas)) {
|
|
||||||
let old_version = self.get().version_id;
|
|
||||||
|
|
||||||
update_fn(self.versions.back_mut().unwrap());
|
|
||||||
|
|
||||||
// Check whether there has been a new change
|
|
||||||
if old_version != self.get().version_id {
|
|
||||||
// This is a linear history,
|
|
||||||
// so we first check if there are newer versions, if so
|
|
||||||
// this means we are in the past, so a change in the past destroys the future
|
|
||||||
// and creates a new future.
|
|
||||||
if self.has_newer_versions() {
|
|
||||||
self.versions = self.versions.take(self.curr_version + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
// Stiletto
|
||||||
|
// Copyright (C) 2020 Stiletto Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use im::Vector;
|
||||||
|
|
||||||
|
use super::tool_ctx::{CanvasToolCtx};
|
||||||
|
use crate::canvas::Canvas;
|
||||||
|
use crate::history::VersionedCanvas;
|
||||||
|
use crate::DocumentSnapshot;
|
||||||
|
|
||||||
|
use druid::widget::prelude::*;
|
||||||
|
use druid::{Color, Data, Env, Event, PointerType};
|
||||||
|
|
||||||
|
#[derive(Clone, Data)]
|
||||||
|
pub struct CanvasState {
|
||||||
|
versioned_canvas: VersionedCanvas,
|
||||||
|
tool_ctx: CanvasToolCtx,
|
||||||
|
temporary_erasing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanvasState {
|
||||||
|
pub fn new(tool_ctx: CanvasToolCtx) -> Self {
|
||||||
|
CanvasState {
|
||||||
|
versioned_canvas: VersionedCanvas::new(Canvas::new()),
|
||||||
|
tool_ctx: tool_ctx,
|
||||||
|
temporary_erasing: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn perform_undo(&mut self) {
|
||||||
|
//if !self.is_drawing() {
|
||||||
|
self.versioned_canvas.undo();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn perform_redo(&mut self) {
|
||||||
|
//if !self.is_drawing() {
|
||||||
|
self.versioned_canvas.redo();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_document_snapshot(&self) -> DocumentSnapshot {
|
||||||
|
DocumentSnapshot {
|
||||||
|
format_version_major: 0,
|
||||||
|
format_version_minor: 1,
|
||||||
|
canvas_elements: self
|
||||||
|
.versioned_canvas
|
||||||
|
.get()
|
||||||
|
.elements()
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_from_snapshot(&mut self, snapshot: DocumentSnapshot) {
|
||||||
|
self.versioned_canvas = VersionedCanvas::new(Canvas::new_with_elements(Vector::from(
|
||||||
|
snapshot.canvas_elements,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_tool_ctx(&mut self, ctx: CanvasToolCtx) {
|
||||||
|
self.tool_ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tool_ctx(&self) -> &CanvasToolCtx {
|
||||||
|
&self.tool_ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tool_ctx_mut(&mut self) -> &mut CanvasToolCtx {
|
||||||
|
&mut self.tool_ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_event(&mut self, mut ctx: &mut EventCtx, event: &Event, env: &Env) {
|
||||||
|
self.tool_ctx
|
||||||
|
.handle_event(ctx, event, &mut self.versioned_canvas, env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CanvasWidget;
|
||||||
|
|
||||||
|
impl Widget<CanvasState> for CanvasWidget {
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut CanvasState, env: &Env) {
|
||||||
|
ctx.request_focus();
|
||||||
|
let mut toggle_eraser_event = false;
|
||||||
|
let mut enable_temporary_erasing = false;
|
||||||
|
match event {
|
||||||
|
Event::MouseDown(mouse_event) => {
|
||||||
|
toggle_eraser_event = true;
|
||||||
|
enable_temporary_erasing = mouse_event.pointer_type == PointerType::Eraser;
|
||||||
|
}
|
||||||
|
Event::MouseMove(mouse_event) => {
|
||||||
|
toggle_eraser_event = true;
|
||||||
|
enable_temporary_erasing = mouse_event.pointer_type == PointerType::Eraser;
|
||||||
|
}
|
||||||
|
Event::MouseUp(mouse_event) => {
|
||||||
|
toggle_eraser_event = true;
|
||||||
|
enable_temporary_erasing = mouse_event.pointer_type == PointerType::Eraser;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
// TODO: the first eraser toggle is not handled
|
||||||
|
if toggle_eraser_event && data.temporary_erasing != enable_temporary_erasing {
|
||||||
|
if enable_temporary_erasing {
|
||||||
|
ctx.submit_notification(crate::commands::PUSH_ERASER);
|
||||||
|
} else {
|
||||||
|
ctx.submit_notification(crate::commands::POP_ERASER);
|
||||||
|
}
|
||||||
|
data.temporary_erasing = enable_temporary_erasing;
|
||||||
|
} else {
|
||||||
|
data.handle_event(ctx, event, env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lifecycle(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut LifeCycleCtx,
|
||||||
|
_event: &LifeCycle,
|
||||||
|
_data: &CanvasState,
|
||||||
|
_env: &Env,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut UpdateCtx,
|
||||||
|
_old_data: &CanvasState,
|
||||||
|
_data: &CanvasState,
|
||||||
|
_env: &Env,
|
||||||
|
) {
|
||||||
|
// the current_element is moved to the versioned_canvas array, no need to repaint
|
||||||
|
//if old_data.is_drawing() && !data.is_drawing() {
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
//if data.is_drawing() {
|
||||||
|
// if let Some(e) = data.current_element.as_ref() {
|
||||||
|
// ctx.request_paint_rect(e.bounding_box());
|
||||||
|
// }
|
||||||
|
//} else {
|
||||||
|
ctx.request_paint();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
_layout_ctx: &mut LayoutCtx,
|
||||||
|
bc: &BoxConstraints,
|
||||||
|
_data: &CanvasState,
|
||||||
|
_env: &Env,
|
||||||
|
) -> Size {
|
||||||
|
// BoxConstraints are passed by the parent widget.
|
||||||
|
// This method can return any Size within those constraints:
|
||||||
|
// bc.constrain(my_size)
|
||||||
|
//
|
||||||
|
// To check if a dimension is infinite or not (e.g. scrolling):
|
||||||
|
// bc.is_width_bounded() / bc.is_height_bounded()
|
||||||
|
bc.max()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The paint method gets called last, after an event flow.
|
||||||
|
// It goes event -> update -> layout -> paint, and each method can influence the next.
|
||||||
|
// Basically, anything that changes the appearance of a widget causes a paint.
|
||||||
|
fn paint(&mut self, ctx: &mut PaintCtx, data: &CanvasState, env: &Env) {
|
||||||
|
// (ctx.size() returns the size of the layout rect we're painting in)
|
||||||
|
let size = ctx.size();
|
||||||
|
let rect = size.to_rect();
|
||||||
|
|
||||||
|
ctx.fill(rect, &Color::WHITE);
|
||||||
|
for element in data.versioned_canvas.get().elements().iter() {
|
||||||
|
element.draw(ctx);
|
||||||
|
}
|
||||||
|
if data.tool_ctx.needs_repaint() {
|
||||||
|
data.tool_ctx.paint(ctx, env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
pub mod canvas;
|
||||||
|
pub mod tool_icon;
|
||||||
|
pub mod tool_ctx;
|
||||||
|
|
||||||
|
pub use canvas::*;
|
||||||
|
pub use tool_icon::*;
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
// Stiletto
|
||||||
|
// Copyright (C) 2020 Stiletto Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::tool::{CanvasToolParams, CanvasToolType};
|
||||||
|
use crate::canvas::{Canvas, CanvasElement};
|
||||||
|
use crate::history::VersionedCanvas;
|
||||||
|
|
||||||
|
use druid::kurbo::BezPath;
|
||||||
|
use druid::{Data, Env, Event, EventCtx, MouseButton, MouseEvent, PaintCtx};
|
||||||
|
|
||||||
|
#[derive(Clone, Data)]
|
||||||
|
pub enum CanvasToolState {
|
||||||
|
Idle,
|
||||||
|
DrawingFreehand {
|
||||||
|
pen_params: CanvasToolParams,
|
||||||
|
current_path: CanvasElement,
|
||||||
|
},
|
||||||
|
Erasing,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Data)]
|
||||||
|
pub struct CanvasToolCtx {
|
||||||
|
initial_params: CanvasToolParams,
|
||||||
|
state: CanvasToolState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanvasToolParams {
|
||||||
|
pub fn tool_type(&self) -> CanvasToolType {
|
||||||
|
match self {
|
||||||
|
CanvasToolParams::Pen { .. } => CanvasToolType::Pen,
|
||||||
|
CanvasToolParams::Eraser => CanvasToolType::Eraser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pressed(mouse_event: &MouseEvent) -> bool {
|
||||||
|
mouse_event.buttons.contains(MouseButton::Left)
|
||||||
|
|| mouse_event.button == MouseButton::Left
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanvasToolCtx {
|
||||||
|
pub fn new(params: CanvasToolParams) -> Self {
|
||||||
|
CanvasToolCtx {
|
||||||
|
initial_params: params,
|
||||||
|
state: CanvasToolState::Idle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
ctx: &EventCtx,
|
||||||
|
event: &Event,
|
||||||
|
mut vcanvas: &mut VersionedCanvas,
|
||||||
|
env: &Env,
|
||||||
|
) {
|
||||||
|
match self.initial_params.tool_type() {
|
||||||
|
CanvasToolType::Pen => self.handle_pen_event(&ctx, &event, &mut vcanvas, &env),
|
||||||
|
CanvasToolType::Eraser => self.handle_erase_event(&ctx, &event, &mut vcanvas, &env),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_erase_event(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &EventCtx,
|
||||||
|
event: &Event,
|
||||||
|
vcanvas: &mut VersionedCanvas,
|
||||||
|
_env: &Env,
|
||||||
|
) {
|
||||||
|
match (&mut self.state, event) {
|
||||||
|
(CanvasToolState::Idle, Event::MouseDown(mouse_event)) if pressed(mouse_event) => {
|
||||||
|
self.state = CanvasToolState::Erasing;
|
||||||
|
}
|
||||||
|
(CanvasToolState::Erasing, Event::MouseMove(mouse_event)) if pressed(mouse_event) => {
|
||||||
|
let eraser_rect = druid::Rect::from_center_size(mouse_event.pos, (5.0, 5.0));
|
||||||
|
let old_elements = vcanvas.get().elements();
|
||||||
|
let mut new_elements = old_elements.clone();
|
||||||
|
new_elements.retain(|elem| {
|
||||||
|
// Check if the element intersects the eraser rect
|
||||||
|
if elem.bounding_box().intersect(eraser_rect).area() > 0.0 {
|
||||||
|
if elem.intersects_rect(eraser_rect) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if new_elements.len() != old_elements.len() {
|
||||||
|
vcanvas.update(|canvas: &mut Canvas| {
|
||||||
|
*canvas = Canvas::new_with_elements(new_elements);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(CanvasToolState::Erasing, Event::MouseUp(mouse_event)) if pressed(mouse_event) => {
|
||||||
|
self.state = CanvasToolState::Idle;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_pen_event(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &EventCtx,
|
||||||
|
event: &Event,
|
||||||
|
vcanvas: &mut VersionedCanvas,
|
||||||
|
_env: &Env,
|
||||||
|
) {
|
||||||
|
match (&mut self.state, event) {
|
||||||
|
(CanvasToolState::Idle, Event::MouseDown(mouse_event)) if pressed(mouse_event) => {
|
||||||
|
let mut kurbo_path = BezPath::new();
|
||||||
|
kurbo_path.move_to((mouse_event.pos.x, mouse_event.pos.y));
|
||||||
|
if let CanvasToolParams::Pen { thickness, color } = &self.initial_params {
|
||||||
|
self.state = CanvasToolState::DrawingFreehand {
|
||||||
|
pen_params: self.initial_params.clone(),
|
||||||
|
current_path: CanvasElement::Freehand {
|
||||||
|
path: crate::canvas::Path { kurbo_path },
|
||||||
|
thickness: *thickness,
|
||||||
|
stroke_color: color.clone(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(
|
||||||
|
CanvasToolState::DrawingFreehand {
|
||||||
|
ref mut current_path,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
Event::MouseMove(mouse_event),
|
||||||
|
) => if pressed(mouse_event) {
|
||||||
|
if let CanvasElement::Freehand { ref mut path, .. } = current_path {
|
||||||
|
path.kurbo_path
|
||||||
|
.line_to((mouse_event.pos.x, mouse_event.pos.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(CanvasToolState::DrawingFreehand { .. }, Event::MouseUp(mouse_event)) if pressed(mouse_event) => {
|
||||||
|
vcanvas.update(move |canvas: &mut Canvas| {
|
||||||
|
let current_state = std::mem::replace(&mut self.state, CanvasToolState::Idle);
|
||||||
|
if let CanvasToolState::DrawingFreehand { current_path, .. } = current_state {
|
||||||
|
if let CanvasElement::Freehand {
|
||||||
|
mut path,
|
||||||
|
mut thickness,
|
||||||
|
stroke_color,
|
||||||
|
} = current_path
|
||||||
|
{
|
||||||
|
canvas.add_element(CanvasElement::Freehand {
|
||||||
|
path,
|
||||||
|
thickness,
|
||||||
|
stroke_color,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_repaint(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paint(&self, ctx: &mut PaintCtx, _env: &Env) {
|
||||||
|
match &self.state {
|
||||||
|
CanvasToolState::DrawingFreehand { current_path, .. } => current_path.draw(ctx),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Stiletto
|
||||||
|
// Copyright (C) 2020 Stiletto Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use druid::widget::{Container, SizedBox, Svg, SvgData, ViewSwitcher, WidgetExt};
|
||||||
|
use druid::{Color, Data, UnitPoint};
|
||||||
|
|
||||||
|
use crate::tool::CanvasToolParams;
|
||||||
|
|
||||||
|
#[derive(Clone, Data)]
|
||||||
|
pub struct CanvasToolIconState {
|
||||||
|
pub tool_params: CanvasToolParams,
|
||||||
|
pub selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanvasToolIconState {
|
||||||
|
fn svg_data(&self) -> SvgData {
|
||||||
|
match self.tool_params {
|
||||||
|
CanvasToolParams::Pen { .. } => include_str!("../../icons/pen.svg")
|
||||||
|
.parse::<SvgData>()
|
||||||
|
.unwrap(),
|
||||||
|
CanvasToolParams::Eraser => include_str!("../../icons/eraser.svg")
|
||||||
|
.parse::<SvgData>()
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_simple_tool_widget(
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
padding: f64,
|
||||||
|
) -> ViewSwitcher<CanvasToolIconState, CanvasToolIconState> {
|
||||||
|
ViewSwitcher::<CanvasToolIconState, CanvasToolIconState>::new(
|
||||||
|
|tool_state, _| tool_state.clone(),
|
||||||
|
move |_, tool_state, _| {
|
||||||
|
Box::new(
|
||||||
|
Container::new(
|
||||||
|
SizedBox::new(
|
||||||
|
Svg::new(tool_state.svg_data())
|
||||||
|
.align_horizontal(UnitPoint::CENTER)
|
||||||
|
.fix_width(width)
|
||||||
|
.fix_height(height),
|
||||||
|
)
|
||||||
|
.padding(padding),
|
||||||
|
)
|
||||||
|
.border(
|
||||||
|
if tool_state.selected {
|
||||||
|
Color::rgb(10.0, 10.0, 10.0)
|
||||||
|
} else {
|
||||||
|
Color::BLACK
|
||||||
|
},
|
||||||
|
1.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue