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"
|
||||
authors = ["Enrico Lumetti <enrico.lumetti@gmail.com>"]
|
||||
edition = "2018"
|
||||
default-run = "stiletto"
|
||||
|
||||
[dependencies]
|
||||
udev = "0.2"
|
||||
libc = "0.2"
|
||||
errno = "0.2"
|
||||
csv = "1.1"
|
||||
druid = { version = "0.6.0", features = ["im"] }
|
||||
log = "0.4"
|
||||
druid = { version = "0.7.0", features = ["im", "svg"] }
|
||||
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]
|
||||
druid = { git = "https://github.com/doppioandante/druid", branch = "stylus_events_0.6.0", features = ["im"] }
|
||||
|
||||
[dependencies.gtk]
|
||||
version = "0.8.1"
|
||||
[target.'cfg(target_os="linux")'.dependencies.gtk]
|
||||
version = "0.9.2"
|
||||
features = ["v3_22"]
|
||||
|
||||
[dependencies.gio]
|
||||
version = "0.8.1"
|
||||
[target.'cfg(target_os="linux")'.dependencies.gio]
|
||||
version = "0.9.1"
|
||||
features = ["v2_56"]
|
||||
|
||||
[dependencies.gdk]
|
||||
version = "0.12.1"
|
||||
[target.'cfg(target_os="linux")'.dependencies.gdk]
|
||||
version = "0.13.2"
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
// 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 {
|
||||
start_pos: f64,
|
||||
end_pos: f64,
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
fn new(x1: f64, x2: f64) -> Interval {
|
||||
Interval {
|
||||
start_pos: x1.min(x2),
|
||||
end_pos: x1.max(x2),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_ordered(start: f64, end: f64) -> Interval {
|
||||
Interval {
|
||||
start_pos: start,
|
||||
end_pos: end,
|
||||
}
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Self) -> bool {
|
||||
self.start_pos < other.end_pos && other.start_pos < self.end_pos
|
||||
}
|
||||
}
|
||||
|
||||
// O(1) complexity
|
||||
// TODO(francesco): Implement this in a way that is not so flamboyant
|
||||
// TODO(francesco): Implement intersection test for bezier curves
|
||||
fn segment_intersects_rect(segment: &druid::kurbo::PathSeg, rect: druid::Rect) -> bool {
|
||||
match segment {
|
||||
druid::kurbo::PathSeg::Line(line) => {
|
||||
// A Segment intersects the rect
|
||||
// if their projections on the x and y line both overlap
|
||||
let line_x_proj = Interval::new(line.p0.x, line.p1.x);
|
||||
let line_y_proj = Interval::new(line.p0.y, line.p1.y);
|
||||
|
||||
let rect_x_proj = Interval::new_ordered(rect.min_x(), rect.max_x());
|
||||
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)
|
||||
}
|
||||
|
||||
_ => {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, druid::Data, Serialize, Deserialize)]
|
||||
pub enum CanvasElement {
|
||||
Freehand {
|
||||
path: Path,
|
||||
thickness: f64,
|
||||
#[serde(with = "ColorDef")]
|
||||
stroke_color: druid::Color,
|
||||
},
|
||||
}
|
||||
|
||||
impl CanvasElement {
|
||||
pub fn bounding_box(&self) -> druid::Rect {
|
||||
match self {
|
||||
CanvasElement::Freehand {
|
||||
path, thickness, ..
|
||||
} => {
|
||||
use druid::kurbo::Shape;
|
||||
path.kurbo_path
|
||||
.bounding_box()
|
||||
.inflate(*thickness, *thickness)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersects_rect(&self, rect: druid::Rect) -> bool {
|
||||
match self {
|
||||
CanvasElement::Freehand { path, .. } => {
|
||||
// TODO(enrico) this doesn't handle thickness
|
||||
path.kurbo_path
|
||||
.segments()
|
||||
.any(|segment| segment_intersects_rect(&segment, rect))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, ctx: &mut druid::PaintCtx) {
|
||||
match self {
|
||||
CanvasElement::Freehand {
|
||||
path,
|
||||
thickness,
|
||||
stroke_color,
|
||||
} => {
|
||||
use druid::RenderContext;
|
||||
ctx.stroke(&path.kurbo_path, &*stroke_color, *thickness);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, druid::Data)]
|
||||
pub struct Canvas {
|
||||
elements: Vector<CanvasElement>,
|
||||
content_size: druid::Size,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
pub fn new() -> Self {
|
||||
Canvas {
|
||||
elements: vector![],
|
||||
content_size: druid::Size::new(0.0, 0.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_elements(elements: Vector<CanvasElement>) -> Self {
|
||||
let mut cv = Canvas::new();
|
||||
for e in elements {
|
||||
cv.add_element(e);
|
||||
}
|
||||
cv
|
||||
}
|
||||
|
||||
pub fn add_element(&mut self, element: CanvasElement) {
|
||||
self.content_size = self
|
||||
.content_size
|
||||
.to_rect()
|
||||
.union(element.bounding_box())
|
||||
.size();
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
148
src/lib.rs
148
src/lib.rs
|
|
@ -1,112 +1,48 @@
|
|||
#[derive(Clone, druid::Data)]
|
||||
pub struct Path {
|
||||
pub kurbo_path: 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 serde::{Deserialize, Serialize};
|
||||
use std::vec::Vec;
|
||||
|
||||
pub mod canvas;
|
||||
pub mod history;
|
||||
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");
|
||||
}
|
||||
|
||||
#[derive(Clone, druid::Data)]
|
||||
pub enum CanvasElement {
|
||||
Freehand { path: Path, thickness: f64 },
|
||||
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 CanvasElement {
|
||||
pub fn bounding_box(&self) -> druid::Rect {
|
||||
match self {
|
||||
CanvasElement::Freehand { path, thickness } => {
|
||||
use druid::kurbo::Shape;
|
||||
path.kurbo_path
|
||||
.bounding_box()
|
||||
.inflate(*thickness, *thickness)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn draw(&self, ctx: &mut druid::PaintCtx) {
|
||||
match self {
|
||||
CanvasElement::Freehand { path, thickness } => {
|
||||
use druid::RenderContext;
|
||||
let stroke_color = druid::Color::rgb8(0, 128, 0);
|
||||
ctx.stroke(&path.kurbo_path, &stroke_color, *thickness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_path_mut(&mut self) -> Option<&mut Path> {
|
||||
match self {
|
||||
CanvasElement::Freehand { path, .. } => Some(path),
|
||||
}
|
||||
impl DocumentSnapshot {
|
||||
pub fn to_writer<W: std::io::Write>(&self, writer: W) -> Result<(), serde_bare::Error> {
|
||||
serde_bare::to_writer(writer, &self)
|
||||
}
|
||||
}
|
||||
|
||||
use im::Vector;
|
||||
|
||||
// A canvas contains all elements to be drawn
|
||||
pub type Canvas = Vector<CanvasElement>;
|
||||
|
||||
#[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 {
|
||||
let focus = self.versions.focus();
|
||||
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 = self.curr_version - 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redo(&mut self) {
|
||||
if self.has_newer_versions() {
|
||||
self.curr_version = self.curr_version + 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, update_fn: impl FnOnce(&Canvas) -> Canvas) {
|
||||
// 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);
|
||||
}
|
||||
let new_version = update_fn(self.get());
|
||||
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)) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
update_fn(self.versions.back_mut().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
401
src/main.rs
401
src/main.rs
|
|
@ -14,173 +14,282 @@
|
|||
// 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::im::{vector};
|
||||
use druid::kurbo::BezPath;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use log::{info, warn};
|
||||
|
||||
use druid::commands;
|
||||
use druid::im::vector;
|
||||
use druid::widget::prelude::*;
|
||||
use druid::{AppLauncher, Color, Data, Event, LocalizedString, WindowDesc};
|
||||
use druid::widget::{
|
||||
Align, Button, Controller, CrossAxisAlignment, Flex, List, SizedBox, WidgetExt,
|
||||
};
|
||||
use druid::{
|
||||
AppDelegate, AppLauncher, Color, Command, Data, DelegateCtx, Env, FileDialogOptions, FileSpec,
|
||||
Handled, Lens, Target, WindowDesc,
|
||||
};
|
||||
|
||||
use stiletto::{CanvasElement, VersionedCanvas, Canvas};
|
||||
use im::Vector;
|
||||
|
||||
#[derive(Clone, Data)]
|
||||
struct CanvasData {
|
||||
current_element: Option<CanvasElement>,
|
||||
//elements: Vector<CanvasElement>,
|
||||
elements: 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");
|
||||
}
|
||||
|
||||
impl CanvasData {
|
||||
fn is_drawing(&self) -> bool {
|
||||
self.current_element.is_some()
|
||||
}
|
||||
#[derive(Clone, Data, Lens)]
|
||||
struct StilettoState {
|
||||
canvas: CanvasState,
|
||||
tool_icons: Vector<CanvasToolIconState>,
|
||||
current_tool: usize,
|
||||
eraser_tool_id: usize,
|
||||
#[data(same_fn = "PartialEq::eq")]
|
||||
current_file_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn perform_undo(&mut self) {
|
||||
if !self.is_drawing() {
|
||||
self.elements.undo();
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_redo(&mut self) {
|
||||
if !self.is_drawing() {
|
||||
self.elements.redo();
|
||||
}
|
||||
impl StilettoState {
|
||||
pub fn set_tool(&mut self, new_tool: usize) {
|
||||
let old_tool = self.current_tool;
|
||||
info!("Changing active tool to: tool #{}", new_tool);
|
||||
self.tool_icons.get_mut(old_tool).unwrap().selected = false;
|
||||
self.tool_icons.get_mut(new_tool).unwrap().selected = true;
|
||||
self.current_tool = new_tool;
|
||||
self.canvas.set_tool_ctx(CanvasToolCtx::new(
|
||||
self.tool_icons.get(new_tool).unwrap().tool_params.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
fn event(&mut self, _ctx: &mut EventCtx, event: &Event, data: &mut CanvasData, _env: &Env) {
|
||||
match event {
|
||||
Event::MouseDown(mouse_event) => {
|
||||
let mut kurbo_path = BezPath::new();
|
||||
kurbo_path.move_to((mouse_event.pos.x, mouse_event.pos.y));
|
||||
data.current_element = Some(CanvasElement::Freehand {
|
||||
path: stiletto::Path { kurbo_path },
|
||||
thickness: 2.0,
|
||||
});
|
||||
}
|
||||
Event::MouseMove(mouse_event) => {
|
||||
if data.is_drawing() {
|
||||
if let Some(current_element) = data.current_element.as_mut() {
|
||||
current_element
|
||||
.get_path_mut()
|
||||
.unwrap()
|
||||
.kurbo_path
|
||||
.line_to((mouse_event.pos.x, mouse_event.pos.y));
|
||||
}
|
||||
let stlt = FileSpec::new("Stiletto notebook", &["stlt"]);
|
||||
let save_dialog_options = FileDialogOptions::new()
|
||||
.allowed_types(vec![stlt])
|
||||
.default_type(stlt);
|
||||
let save_dialog_options_clone = save_dialog_options.clone();
|
||||
let open_dialog_options = save_dialog_options.clone();
|
||||
|
||||
let save_buttons = Flex::row()
|
||||
.cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.with_child(
|
||||
Button::new("Save").on_click(move |ctx, data: &mut StilettoState, _| {
|
||||
if data.current_file_path.is_some() {
|
||||
ctx.submit_command(Command::new(commands::SAVE_FILE, (), Target::Auto));
|
||||
} else {
|
||||
ctx.submit_command(Command::new(
|
||||
druid::commands::SHOW_SAVE_PANEL,
|
||||
save_dialog_options_clone.clone(),
|
||||
Target::Auto,
|
||||
))
|
||||
}
|
||||
}
|
||||
Event::MouseUp(_) => {
|
||||
if data.is_drawing() {
|
||||
if let Some(current_element) = data.current_element.take() {
|
||||
}),
|
||||
)
|
||||
.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,
|
||||
))
|
||||
}));
|
||||
|
||||
data.elements.update(move |canvas: &Canvas| -> Canvas {
|
||||
let mut new_canvas = canvas.clone();
|
||||
new_canvas.push_back(current_element);
|
||||
return new_canvas;
|
||||
});
|
||||
let tool_buttons = Flex::row()
|
||||
.cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.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, _| {
|
||||
tool_state.selected = true;
|
||||
ctx.submit_notification(stiletto::commands::UPDATE_TOOL);
|
||||
})
|
||||
})
|
||||
.horizontal()
|
||||
.lens(StilettoState::tool_icons),
|
||||
1.0,
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn lifecycle(
|
||||
&mut self,
|
||||
_ctx: &mut LifeCycleCtx,
|
||||
_event: &LifeCycle,
|
||||
_data: &CanvasData,
|
||||
_env: &Env,
|
||||
) {
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
ctx: &mut UpdateCtx,
|
||||
old_data: &CanvasData,
|
||||
data: &CanvasData,
|
||||
_env: &Env,
|
||||
) {
|
||||
// the current_element is moved to the elements 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: &CanvasData,
|
||||
_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: &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.elements.get().iter() {
|
||||
element.draw(ctx);
|
||||
}
|
||||
if let Some(element) = &data.current_element {
|
||||
element.draw(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_ui() -> impl Widget<CanvasData> {
|
||||
use druid::widget::{Align, Button, CrossAxisAlignment, Flex, SizedBox};
|
||||
let toolbar = Flex::row()
|
||||
.cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.with_spacer(30.0)
|
||||
.with_child(
|
||||
Button::new("Undo").on_click(|_ctx: &mut EventCtx, data: &mut CanvasData, _env: &Env| data.perform_undo())
|
||||
)
|
||||
.with_child(Button::new("Redo").on_click(|_ctx: &mut EventCtx, data: &mut CanvasData, _env: &Env| data.perform_redo()));
|
||||
.with_flex_child(Align::left(history_buttons), 1.0)
|
||||
.with_spacer(10.0)
|
||||
.with_flex_child(Align::left(tool_buttons), 2.0)
|
||||
.with_spacer(20.0)
|
||||
.with_flex_child(Align::right(save_buttons), 1.0)
|
||||
.with_spacer(30.0);
|
||||
|
||||
Flex::column()
|
||||
.cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.must_fill_main_axis(true)
|
||||
.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() {
|
||||
let window = WindowDesc::new(build_ui)
|
||||
.window_size((1024.0, 1400.0))
|
||||
.title(
|
||||
LocalizedString::new("custom-widget-demo-window-title").with_placeholder("Stiletto"),
|
||||
);
|
||||
let canvas_data = CanvasData {
|
||||
current_element: None,
|
||||
elements: VersionedCanvas::new(vector![]),
|
||||
};
|
||||
AppLauncher::with_window(window)
|
||||
.use_simple_logger()
|
||||
.launch(canvas_data)
|
||||
.expect("launch failed");
|
||||
struct ToolSwitcher {
|
||||
saved_tool: usize,
|
||||
}
|
||||
|
||||
impl ToolSwitcher {
|
||||
pub fn new() -> Self {
|
||||
ToolSwitcher { saved_tool: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget<StilettoState>> Controller<StilettoState, W> for ToolSwitcher {
|
||||
fn event(
|
||||
&mut self,
|
||||
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,
|
||||
}
|
||||
|
||||
|
|
@ -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