sync commit
This commit is contained in:
parent
827af45ab9
commit
b0d45a5eec
|
@ -9,6 +9,7 @@ dependencies = [
|
|||
"polars",
|
||||
"rhai",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syntect",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
@ -18,6 +19,7 @@ name = "abacus-ui"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"abacus-core",
|
||||
"clipboard",
|
||||
"druid",
|
||||
"ropey",
|
||||
"syntect",
|
||||
|
@ -160,6 +162,15 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitmaps"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.6"
|
||||
|
@ -252,6 +263,28 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"x11-clipboard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.24.0"
|
||||
|
@ -444,6 +477,15 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193"
|
||||
dependencies = [
|
||||
"matches",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
|
@ -476,12 +518,14 @@ dependencies = [
|
|||
"fluent-langneg",
|
||||
"fluent-syntax",
|
||||
"fnv",
|
||||
"im",
|
||||
"instant",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tracing-wasm",
|
||||
"unic-langid",
|
||||
"unicode-segmentation",
|
||||
"usvg",
|
||||
"xi-unicode",
|
||||
]
|
||||
|
||||
|
@ -578,6 +622,12 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e"
|
||||
|
||||
[[package]]
|
||||
name = "fluent-bundle"
|
||||
version = "0.15.2"
|
||||
|
@ -618,6 +668,17 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e58903f4f8d5b58c7d300908e4ebe5289c1bfdf5587964330f12023b8ff17fd1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"memmap2 0.2.3",
|
||||
"ttf-parser 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
|
@ -953,6 +1014,20 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "im"
|
||||
version = "15.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"rand_core",
|
||||
"rand_xoshiro",
|
||||
"sized-chunks",
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
|
@ -1183,6 +1258,15 @@ version = "2.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.5.7"
|
||||
|
@ -1357,6 +1441,26 @@ dependencies = [
|
|||
"malloc_buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-foundation"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
|
||||
dependencies = [
|
||||
"block",
|
||||
"objc",
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
|
||||
dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.15.0"
|
||||
|
@ -1476,6 +1580,12 @@ dependencies = [
|
|||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
||||
|
||||
[[package]]
|
||||
name = "piet"
|
||||
version = "0.5.0"
|
||||
|
@ -1659,7 +1769,7 @@ dependencies = [
|
|||
"lexical",
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"memmap2 0.5.7",
|
||||
"num",
|
||||
"once_cell",
|
||||
"polars-arrow",
|
||||
|
@ -1820,6 +1930,15 @@ dependencies = [
|
|||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xoshiro"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.5.3"
|
||||
|
@ -1844,6 +1963,12 @@ dependencies = [
|
|||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rctree"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be9e29cb19c8fe84169fcb07f8f11e66bc9e6e0280efd4715c54818296f8a4a8"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
|
@ -1917,6 +2042,15 @@ dependencies = [
|
|||
"str_indices",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b"
|
||||
dependencies = [
|
||||
"xmlparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
|
@ -1938,6 +2072,22 @@ version = "1.0.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab463a295d00f3692e0974a0bfd83c7a9bcd119e27e07c2beecdb1b44a09d10"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytemuck",
|
||||
"smallvec",
|
||||
"ttf-parser 0.9.0",
|
||||
"unicode-bidi-mirroring",
|
||||
"unicode-ccc",
|
||||
"unicode-general-category",
|
||||
"unicode-script",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
|
@ -2011,9 +2161,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.85"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
|
||||
checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -2065,6 +2215,31 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
|
||||
|
||||
[[package]]
|
||||
name = "simplecss"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
|
||||
|
||||
[[package]]
|
||||
name = "sized-chunks"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.7"
|
||||
|
@ -2146,6 +2321,16 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff"
|
||||
dependencies = [
|
||||
"float-cmp",
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.102"
|
||||
|
@ -2333,6 +2518,18 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62ddb402ac6c2af6f7a2844243887631c4e94b51585b229fcfddb43958cd55ca"
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6"
|
||||
|
||||
[[package]]
|
||||
name = "type-map"
|
||||
version = "0.4.0"
|
||||
|
@ -2342,6 +2539,12 @@ dependencies = [
|
|||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.5"
|
||||
|
@ -2417,24 +2620,87 @@ dependencies = [
|
|||
"unic-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi-mirroring"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-general-category"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-script"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-vo"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef8352f317d8f9a918ba5154797fb2a93e2730244041cf7d5be35148266adfa5"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"data-url",
|
||||
"flate2",
|
||||
"fontdb",
|
||||
"kurbo",
|
||||
"log",
|
||||
"memmap2 0.2.3",
|
||||
"pico-args",
|
||||
"rctree",
|
||||
"roxmltree",
|
||||
"rustybuzz",
|
||||
"simplecss",
|
||||
"siphasher",
|
||||
"svgtypes",
|
||||
"ttf-parser 0.12.3",
|
||||
"unicode-bidi",
|
||||
"unicode-script",
|
||||
"unicode-vo",
|
||||
"xmlwriter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf16_lit"
|
||||
version = "2.0.2"
|
||||
|
@ -2629,6 +2895,25 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-clipboard"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea"
|
||||
dependencies = [
|
||||
"xcb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcb"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xi-unicode"
|
||||
version = "0.3.0"
|
||||
|
@ -2641,6 +2926,18 @@ version = "0.8.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "xmlparser"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"
|
||||
|
||||
[[package]]
|
||||
name = "xmlwriter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
|
|
|
@ -11,5 +11,6 @@ syntect = "5.0.0"
|
|||
rhai = "1.10.1"
|
||||
polars = { version = "0.24.3", features = ["lazy", "rows"] }
|
||||
tracing-subscriber = "0.3"
|
||||
serde_json = "1.0.86"
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,9 @@ pub fn setup_engine(engine: &mut rhai::Engine) {
|
|||
engine.register_type::<Series>();
|
||||
engine.register_indexer_get(Series::s_get);
|
||||
engine.register_fn("series", script_functions::series);
|
||||
engine.register_fn("to_series", script_functions::to_series);
|
||||
engine.register_fn("to_series", script_functions::to_series_unnamed);
|
||||
engine.register_fn("series", script_functions::series_unnamed);
|
||||
engine.register_fn("head", Series::s_head);
|
||||
engine.register_fn("+", Series::add);
|
||||
engine.register_fn("sort", Series::s_sort);
|
||||
|
@ -51,7 +54,7 @@ pub fn setup_engine(engine: &mut rhai::Engine) {
|
|||
engine.register_fn("first", script_functions::first);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DataFrame(polars::frame::DataFrame);
|
||||
|
||||
impl Deref for DataFrame {
|
||||
|
@ -72,6 +75,8 @@ impl DataFrame {
|
|||
pub fn load_csv(path: &str) -> ScriptResult<DataFrame> {
|
||||
let df = polars::io::csv::CsvReader::from_path(path)
|
||||
.map_err(|e| e.to_string())?
|
||||
.infer_schema(Some(1))
|
||||
.with_ignore_parser_errors(true)
|
||||
.has_header(true)
|
||||
.finish()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
@ -158,7 +163,7 @@ impl From<rhai::Dynamic> for DataFrameExpression {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Series(polars::series::Series);
|
||||
|
||||
impl Deref for Series {
|
||||
|
@ -288,6 +293,21 @@ mod script_functions {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn series_unnamed(arr: rhai::Array) -> std::result::Result<Series, Box<EvalAltResult>> {
|
||||
series("Unnamed", arr)
|
||||
}
|
||||
|
||||
pub fn to_series_unnamed(arr: rhai::Array) -> std::result::Result<Series, Box<EvalAltResult>> {
|
||||
series("Unnamed", arr)
|
||||
}
|
||||
|
||||
pub fn to_series(
|
||||
arr: rhai::Array,
|
||||
name: &str,
|
||||
) -> std::result::Result<Series, Box<EvalAltResult>> {
|
||||
series(name, arr)
|
||||
}
|
||||
|
||||
pub fn series(name: &str, arr: rhai::Array) -> std::result::Result<Series, Box<EvalAltResult>> {
|
||||
if let Some(i) = arr.first() {
|
||||
let series = if i.type_id() == TypeId::of::<i64>() {
|
||||
|
|
|
@ -40,6 +40,21 @@ impl Engine {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_script(&mut self, script: &str) -> Output {
|
||||
match self.engine.eval::<rhai::Dynamic>(script) {
|
||||
Ok(res) if res.is::<dataframe::DataFrame>() => {
|
||||
let frame = rhai::Dynamic::cast::<dataframe::DataFrame>(res);
|
||||
Output::DataFrame(frame)
|
||||
}
|
||||
Ok(res) if res.is::<dataframe::Series>() => {
|
||||
let frame = rhai::Dynamic::cast::<dataframe::Series>(res);
|
||||
Output::Series(frame)
|
||||
}
|
||||
Ok(res) => Output::Scalar(res),
|
||||
Err(e) => Output::Error(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -51,6 +66,24 @@ pub enum Output {
|
|||
Error(String),
|
||||
}
|
||||
|
||||
impl Default for Output {
|
||||
fn default() -> Self {
|
||||
Output::None
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Output {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Scalar(l0), Self::Scalar(r0)) => l0.to_string() == r0.to_string(),
|
||||
(Self::DataFrame(l0), Self::DataFrame(r0)) => l0 == r0,
|
||||
(Self::Series(l0), Self::Series(r0)) => l0 == r0,
|
||||
(Self::Error(l0), Self::Error(r0)) => l0 == r0,
|
||||
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn into_frame(self) -> dataframe::DataFrame {
|
||||
if let Self::DataFrame(v) = self {
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
mod dataframe;
|
||||
mod engine;
|
||||
mod save_file;
|
||||
|
||||
pub use engine::Block;
|
||||
pub use engine::Engine;
|
||||
pub use engine::Output;
|
||||
pub use save_file::{SaveBlock, SaveFile};
|
||||
|
||||
use rhai::EvalAltResult;
|
||||
type ScriptResult<T> = std::result::Result<T, Box<EvalAltResult>>;
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct SaveBlock {
|
||||
pub name: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl SaveBlock {
|
||||
pub fn new<S: Display>(name: &str, content: S) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
content: content.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct SaveFile {
|
||||
#[serde(skip)]
|
||||
pub filepath: String,
|
||||
pub blocks: Vec<SaveBlock>,
|
||||
}
|
||||
|
||||
impl SaveFile {
|
||||
pub fn new(filepath: String, blocks: Vec<SaveBlock>) -> Self {
|
||||
Self { filepath, blocks }
|
||||
}
|
||||
|
||||
pub fn open(filepath: &str) -> Result<Self, std::io::Error> {
|
||||
let data = std::fs::read_to_string(filepath)?;
|
||||
let mut slf = serde_json::from_str::<Self>(&data)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
|
||||
slf.filepath = filepath.to_string();
|
||||
Ok(slf)
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<(), std::io::Error> {
|
||||
let s = serde_json::to_vec_pretty(&self)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
|
||||
std::fs::write(&self.filepath, s)
|
||||
}
|
||||
}
|
|
@ -6,7 +6,8 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
druid = { git = "https://github.com/linebender/druid.git" }
|
||||
druid = { git = "https://github.com/linebender/druid.git", features=["im", "svg"] }
|
||||
abacus-core = { path = "../abacus-core" }
|
||||
syntect = "5.0.0"
|
||||
ropey = "1.5.0"
|
||||
ropey = "1.5.0"
|
||||
clipboard = "0.5.0"
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="ban.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.0058594"
|
||||
inkscape:cx="255.75073"
|
||||
inkscape:cy="255.75073"
|
||||
inkscape:window-width="1830"
|
||||
inkscape:window-height="1980"
|
||||
inkscape:window-x="6470"
|
||||
inkscape:window-y="80"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="M367.2 412.5L99.5 144.8C77.1 176.1 64 214.5 64 256c0 106 86 192 192 192c41.5 0 79.9-13.1 111.2-35.5zm45.3-45.3C434.9 335.9 448 297.5 448 256c0-106-86-192-192-192c-41.5 0-79.9 13.1-111.2 35.5L412.5 367.2zM512 256c0 141.4-114.6 256-256 256S0 397.4 0 256S114.6 0 256 0S512 114.6 512 256z"
|
||||
id="path2"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="floppy-disk.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.0058594"
|
||||
inkscape:cx="223.84421"
|
||||
inkscape:cy="256.24927"
|
||||
inkscape:window-width="1830"
|
||||
inkscape:window-height="970"
|
||||
inkscape:window-x="8340"
|
||||
inkscape:window-y="80"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V173.3c0-17-6.7-33.3-18.7-45.3L352 50.7C340 38.7 323.7 32 306.7 32H64zm0 96c0-17.7 14.3-32 32-32H288c17.7 0 32 14.3 32 32v64c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V128zM224 416c-35.3 0-64-28.7-64-64s28.7-64 64-64s64 28.7 64 64s-28.7 64-64 64z"
|
||||
id="path2"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 576 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="folder-open.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.0058594"
|
||||
inkscape:cx="288.15579"
|
||||
inkscape:cy="256.24927"
|
||||
inkscape:window-width="1830"
|
||||
inkscape:window-height="970"
|
||||
inkscape:window-x="8340"
|
||||
inkscape:window-y="80"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="M88.7 223.8L0 375.8V96C0 60.7 28.7 32 64 32H181.5c17 0 33.3 6.7 45.3 18.7l26.5 26.5c12 12 28.3 18.7 45.3 18.7H416c35.3 0 64 28.7 64 64v32H144c-22.8 0-43.8 12.1-55.3 31.8zm27.6 16.1C122.1 230 132.6 224 144 224H544c11.5 0 22 6.1 27.7 16.1s5.7 22.2-.1 32.1l-112 192C453.9 474 443.4 480 432 480H32c-11.5 0-22-6.1-27.7-16.1s-5.7-22.2 .1-32.1l112-192z"
|
||||
id="path2"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 384 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="play.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.0058594"
|
||||
inkscape:cx="191.93768"
|
||||
inkscape:cy="256.24927"
|
||||
inkscape:window-width="1830"
|
||||
inkscape:window-height="1980"
|
||||
inkscape:window-x="6470"
|
||||
inkscape:window-y="80"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"
|
||||
id="path2"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="plus.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.0058594"
|
||||
inkscape:cx="223.84421"
|
||||
inkscape:cy="256.49854"
|
||||
inkscape:window-width="1830"
|
||||
inkscape:window-height="633"
|
||||
inkscape:window-x="8340"
|
||||
inkscape:window-y="753"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32V224H48c-17.7 0-32 14.3-32 32s14.3 32 32 32H192V432c0 17.7 14.3 32 32 32s32-14.3 32-32V288H400c17.7 0 32-14.3 32-32s-14.3-32-32-32H256V80z"
|
||||
id="path2"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="trash.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.0058594"
|
||||
inkscape:cx="223.84421"
|
||||
inkscape:cy="255.75073"
|
||||
inkscape:window-width="1830"
|
||||
inkscape:window-height="1980"
|
||||
inkscape:window-x="6470"
|
||||
inkscape:window-y="80"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"
|
||||
id="path2"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,93 @@
|
|||
use druid::AppDelegate;
|
||||
|
||||
use crate::{
|
||||
commands,
|
||||
data::{AppData, Block},
|
||||
};
|
||||
|
||||
pub struct Delegate;
|
||||
|
||||
impl AppDelegate<AppData> for Delegate {
|
||||
fn command(
|
||||
&mut self,
|
||||
_ctx: &mut druid::DelegateCtx,
|
||||
_target: druid::Target,
|
||||
cmd: &druid::Command,
|
||||
data: &mut AppData,
|
||||
_env: &druid::Env,
|
||||
) -> druid::Handled {
|
||||
if let Some(file_info) = cmd.get(druid::commands::SAVE_FILE_AS) {
|
||||
let filepath = file_info.path().as_os_str().to_str().unwrap().to_string();
|
||||
let _ = abacus_core::SaveFile::new(
|
||||
filepath.clone(),
|
||||
data.blocks
|
||||
.iter()
|
||||
.map(|d| abacus_core::SaveBlock::new(&d.name, &d.editor_data.content))
|
||||
.collect(),
|
||||
)
|
||||
.save();
|
||||
data.filename = Some(filepath);
|
||||
return druid::Handled::Yes;
|
||||
}
|
||||
|
||||
if let Some(file_info) = cmd.get(druid::commands::OPEN_FILE) {
|
||||
let filepath = file_info.path().as_os_str().to_str().unwrap();
|
||||
match abacus_core::SaveFile::open(filepath) {
|
||||
Ok(save_file) => {
|
||||
data.blocks = save_file
|
||||
.blocks
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, b)| Block::new_with_content(&b.name, i, &b.content))
|
||||
.collect();
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
data.filename = Some(filepath.to_string());
|
||||
return druid::Handled::Yes;
|
||||
}
|
||||
|
||||
if cmd.is(commands::PROCESS_WORKBOOK) {
|
||||
data.process();
|
||||
return druid::Handled::Yes;
|
||||
}
|
||||
|
||||
if cmd.is(commands::PROCESS_BLOCK) {
|
||||
if let Some(index) = cmd.get(commands::PROCESS_BLOCK) {
|
||||
data.process_block(*index);
|
||||
return druid::Handled::Yes;
|
||||
}
|
||||
}
|
||||
|
||||
if cmd.is(commands::DELETE_BLOCK) {
|
||||
if let Some(index) = cmd.get(commands::DELETE_BLOCK) {
|
||||
data.blocks.remove(*index);
|
||||
return druid::Handled::Yes;
|
||||
}
|
||||
}
|
||||
|
||||
druid::Handled::No
|
||||
}
|
||||
|
||||
fn event(
|
||||
&mut self,
|
||||
_ctx: &mut druid::DelegateCtx,
|
||||
_window_id: druid::WindowId,
|
||||
event: druid::Event,
|
||||
data: &mut AppData,
|
||||
_env: &druid::Env,
|
||||
) -> Option<druid::Event> {
|
||||
if let druid::Event::KeyDown(ref e) = event {
|
||||
if druid::keyboard_types::Key::Character(String::from("n")) == e.key && e.mods.ctrl() {
|
||||
data.blocks.push_back(Block::new(
|
||||
&format!("Block #{}", data.blocks.len() + 1),
|
||||
data.blocks.len(),
|
||||
));
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(event)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
use druid::{
|
||||
widget::{Container, Controller, Flex, Label, Padding, Painter, Svg, SvgData},
|
||||
Color, Data, LifeCycle, RenderContext, Widget, WidgetExt,
|
||||
};
|
||||
|
||||
use crate::AppData;
|
||||
|
||||
pub fn app_header_ui() -> impl Widget<AppData> {
|
||||
let open_svg = include_str!("../assets/folder-open.svg")
|
||||
.parse::<SvgData>()
|
||||
.unwrap_or_default();
|
||||
|
||||
let disk_svg = include_str!("../assets/floppy-disk.svg")
|
||||
.parse::<SvgData>()
|
||||
.unwrap_or_default();
|
||||
|
||||
let run_svg = include_str!("../assets/play.svg")
|
||||
.parse::<SvgData>()
|
||||
.unwrap_or_default();
|
||||
|
||||
let plus_svg = include_str!("../assets/plus.svg")
|
||||
.parse::<SvgData>()
|
||||
.unwrap_or_default();
|
||||
|
||||
Container::new(
|
||||
Flex::row()
|
||||
.must_fill_main_axis(true)
|
||||
.with_spacer(20.0)
|
||||
.with_child(
|
||||
Container::new(Padding::new(10.0, Svg::new(open_svg).fix_width(10.0)))
|
||||
.controller(ToolbarButtonController::new(Color::rgb8(50, 50, 50)))
|
||||
.on_click(|ctx, _, _| {
|
||||
let abacus = druid::FileSpec::new("Abacus File", &["abacus"]);
|
||||
let json = druid::FileSpec::new("JSON File", &["json"]);
|
||||
|
||||
let open_dialog_options = druid::FileDialogOptions::new()
|
||||
.allowed_types(vec![abacus, json])
|
||||
.default_type(abacus)
|
||||
.name_label("Source")
|
||||
.title("Open Workbook")
|
||||
.button_text("Open");
|
||||
|
||||
ctx.submit_command(
|
||||
druid::commands::SHOW_OPEN_PANEL.with(open_dialog_options),
|
||||
);
|
||||
}),
|
||||
)
|
||||
.with_spacer(10.0)
|
||||
.with_child(
|
||||
Container::new(Padding::new(10.0, Svg::new(disk_svg).fix_width(10.0)))
|
||||
.controller(ToolbarButtonController::new(Color::rgb8(50, 50, 50)))
|
||||
.on_click(|ctx, _, _| {
|
||||
let abacus = druid::FileSpec::new("Abacus File", &["abacus"]);
|
||||
let json = druid::FileSpec::new("JSON File", &["json"]);
|
||||
|
||||
let save_dialog_options = druid::FileDialogOptions::new()
|
||||
.allowed_types(vec![abacus, json])
|
||||
.default_type(abacus)
|
||||
.name_label("Target")
|
||||
.title("Save workbook")
|
||||
.button_text("Save");
|
||||
|
||||
ctx.submit_command(
|
||||
druid::commands::SHOW_SAVE_PANEL.with(save_dialog_options),
|
||||
);
|
||||
}),
|
||||
)
|
||||
.with_spacer(20.0)
|
||||
.with_flex_child(
|
||||
Padding::new(
|
||||
5.0,
|
||||
Container::new(Padding::new(
|
||||
3.0,
|
||||
Label::dynamic(|data: &AppData, _| {
|
||||
data.filename
|
||||
.clone()
|
||||
.unwrap_or_else(|| String::from("Scratch file"))
|
||||
})
|
||||
.with_text_size(12.0)
|
||||
.center(),
|
||||
))
|
||||
.background(Color::rgb8(35, 35, 35))
|
||||
.rounded(4.0)
|
||||
.border(Color::rgb8(10, 10, 10), 1.0)
|
||||
.expand_width(),
|
||||
),
|
||||
1.0,
|
||||
)
|
||||
.with_spacer(20.0)
|
||||
.with_child(
|
||||
Container::new(Padding::new(10.0, Svg::new(plus_svg).fix_width(10.0)))
|
||||
.controller(ToolbarButtonController::new(Color::rgb8(50, 50, 50)))
|
||||
.on_click(|_ctx, data: &mut AppData, _env| {
|
||||
data.blocks.push_back(crate::Block::new(
|
||||
&format!("Block #{}", data.blocks.len() + 1),
|
||||
data.blocks.len(),
|
||||
));
|
||||
}),
|
||||
)
|
||||
.with_spacer(10.0)
|
||||
.with_child(
|
||||
Container::new(Padding::new(10.0, Svg::new(run_svg).fix_width(10.0)))
|
||||
.controller(ToolbarButtonController::new(Color::rgb8(50, 50, 50)))
|
||||
.on_click(|_ctx, data: &mut AppData, _env| data.process()),
|
||||
)
|
||||
.with_spacer(20.0)
|
||||
.expand_width(),
|
||||
)
|
||||
.background(Color::rgb8(20, 20, 20))
|
||||
}
|
||||
|
||||
pub fn header_separater() -> impl Widget<AppData> {
|
||||
Painter::new(|ctx, _data, _env| {
|
||||
let rect = ctx.size().to_rect();
|
||||
ctx.fill(rect, &Color::rgb8(0, 0, 0))
|
||||
})
|
||||
.fix_height(1.0)
|
||||
.expand_width()
|
||||
}
|
||||
|
||||
pub struct ToolbarButtonController {
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl ToolbarButtonController {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self { color }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Data> Controller<T, Container<T>> for ToolbarButtonController {
|
||||
fn event(
|
||||
&mut self,
|
||||
child: &mut Container<T>,
|
||||
ctx: &mut druid::EventCtx,
|
||||
event: &druid::Event,
|
||||
data: &mut T,
|
||||
env: &druid::Env,
|
||||
) {
|
||||
ctx.set_cursor(&druid::Cursor::Pointer);
|
||||
child.event(ctx, event, data, env)
|
||||
}
|
||||
|
||||
fn lifecycle(
|
||||
&mut self,
|
||||
child: &mut Container<T>,
|
||||
ctx: &mut druid::LifeCycleCtx,
|
||||
event: &druid::LifeCycle,
|
||||
data: &T,
|
||||
env: &druid::Env,
|
||||
) {
|
||||
match event {
|
||||
LifeCycle::HotChanged(true) => {
|
||||
child.set_background(self.color.clone());
|
||||
ctx.request_paint();
|
||||
}
|
||||
LifeCycle::HotChanged(false) => {
|
||||
child.set_background(Color::TRANSPARENT);
|
||||
ctx.request_paint();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
child.lifecycle(ctx, event, data, env)
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
child: &mut Container<T>,
|
||||
ctx: &mut druid::UpdateCtx,
|
||||
old_data: &T,
|
||||
data: &T,
|
||||
env: &druid::Env,
|
||||
) {
|
||||
child.update(ctx, old_data, data, env)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
use druid::Selector;
|
||||
|
||||
pub const PROCESS_WORKBOOK: Selector<()> = Selector::new("process-workbook");
|
||||
pub const PROCESS_BLOCK: Selector<usize> = Selector::new("process-block");
|
||||
pub const DELETE_BLOCK: Selector<usize> = Selector::new("delete-block");
|
|
@ -0,0 +1,71 @@
|
|||
use abacus_core::Output;
|
||||
use druid::{
|
||||
im::{vector, Vector},
|
||||
Data, Lens,
|
||||
};
|
||||
|
||||
use crate::editor;
|
||||
|
||||
#[derive(Clone, Data, Lens, Debug)]
|
||||
pub struct AppData {
|
||||
#[data(same_fn = "PartialEq::eq")]
|
||||
pub filename: Option<String>,
|
||||
pub blocks: Vector<Block>,
|
||||
}
|
||||
|
||||
impl Default for AppData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
filename: None,
|
||||
blocks: vector![Block::new("Block #1", 0)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppData {
|
||||
pub fn process(&mut self) {
|
||||
let mut engine = abacus_core::Engine::default();
|
||||
for mut block in self.blocks.iter_mut() {
|
||||
let start = std::time::Instant::now();
|
||||
block.output = engine.process_script(&block.editor_data.content.to_string());
|
||||
println!("block executed in {}", start.elapsed().as_millis());
|
||||
}
|
||||
}
|
||||
pub fn process_block(&mut self, index: usize) {
|
||||
let mut engine = abacus_core::Engine::default();
|
||||
if let Some(block) = self.blocks.get_mut(index) {
|
||||
let start = std::time::Instant::now();
|
||||
block.output = engine.process_script(&block.editor_data.content.to_string());
|
||||
println!("block executed in {}", start.elapsed().as_millis());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Data, Lens, Default, PartialEq, Debug)]
|
||||
pub struct Block {
|
||||
pub name: String,
|
||||
pub editor_data: editor::EditorData,
|
||||
#[data(same_fn = "PartialEq::eq")]
|
||||
pub output: Output,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn new(name: &str, index: usize) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
editor_data: Default::default(),
|
||||
output: Default::default(),
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_content(name: &str, index: usize, content: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
editor_data: editor::EditorData::new(content),
|
||||
output: Default::default(),
|
||||
index,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use abacus_core::Output;
|
||||
use clipboard::ClipboardProvider;
|
||||
use druid::{
|
||||
piet::{CairoTextLayout, Text, TextAttribute, TextLayout, TextLayoutBuilder},
|
||||
Color, Data, Event, FontFamily, Lens, LifeCycle, PaintCtx, Rect, RenderContext, Widget,
|
||||
widget::{Container, Flex, Label, Padding, Svg, SvgData},
|
||||
Color, Data, Event, FontDescriptor, FontFamily, FontWeight, Lens, LifeCycle, PaintCtx, Rect,
|
||||
RenderContext, Target, Widget, WidgetExt,
|
||||
};
|
||||
|
||||
use ropey::Rope;
|
||||
|
@ -10,14 +14,17 @@ use syntect::easy::HighlightLines;
|
|||
use syntect::highlighting::{Style, ThemeSet};
|
||||
use syntect::parsing::SyntaxSet;
|
||||
|
||||
#[derive(Clone, Data, PartialEq, Eq)]
|
||||
use crate::{app_header::ToolbarButtonController, Block};
|
||||
|
||||
const FONT_SIZE: f64 = 16.0;
|
||||
|
||||
#[derive(Clone, Data, PartialEq, Eq, Debug)]
|
||||
pub enum EditMode {
|
||||
Normal,
|
||||
Insert,
|
||||
Visual,
|
||||
}
|
||||
|
||||
#[derive(Data, Lens, Clone)]
|
||||
#[derive(Data, Lens, Clone, PartialEq, Debug)]
|
||||
pub struct EditorData {
|
||||
#[data(same_fn = "PartialEq::eq")]
|
||||
pub content: Rope,
|
||||
|
@ -35,16 +42,27 @@ impl Default for EditorData {
|
|||
cursor_pos: 5,
|
||||
selection_pos: 5,
|
||||
mode: EditMode::Normal,
|
||||
cursor_opactiy: 0.1,
|
||||
cursor_opactiy: 255.0,
|
||||
cursor_fade: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EditorData {
|
||||
pub fn new(content: &str) -> Self {
|
||||
println!("new block: {}", content);
|
||||
Self {
|
||||
content: Rope::from_str(content),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_cursor(&mut self, idx: usize) {
|
||||
self.cursor_pos = idx;
|
||||
self.deselect();
|
||||
if idx <= self.content.len_chars() {
|
||||
self.cursor_pos = idx;
|
||||
self.deselect();
|
||||
// dbg!(self.content.char(self.cursor_pos));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_range(&self) -> Range<usize> {
|
||||
|
@ -78,6 +96,19 @@ impl EditorData {
|
|||
self.selection_pos = self.cursor_pos;
|
||||
}
|
||||
|
||||
pub fn delete_char_forward(&mut self) {
|
||||
if !self.select_range().is_empty() {
|
||||
let range = self.select_range();
|
||||
self.content.remove(self.select_range());
|
||||
self.move_cursor(range.start);
|
||||
return;
|
||||
}
|
||||
|
||||
if self.cursor_pos < self.content.len_chars() {
|
||||
self.content.remove((self.cursor_pos)..self.cursor_pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_char_back(&mut self) {
|
||||
// Delete selection
|
||||
if !self.select_range().is_empty() {
|
||||
|
@ -108,7 +139,6 @@ impl EditorData {
|
|||
let line_pos = self.cursor_pos - start_of_current_line;
|
||||
let up_line_start = self.content.line_to_char(line_idx - 1);
|
||||
let up_line = self.content.line(line_idx - 1);
|
||||
dbg!(line_pos.min(up_line.len_chars()));
|
||||
self.move_cursor(up_line_start + line_pos.min(up_line.len_chars() - 1));
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +161,17 @@ impl EditorData {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn select_to_end_of_line(&mut self) {
|
||||
let line_idx = self.content.char_to_line(self.cursor_pos);
|
||||
let start_of_line = self.content.line_to_char(line_idx);
|
||||
let line = self.content.line(line_idx);
|
||||
if line_idx == self.content.len_lines() - 1 {
|
||||
self.selection_pos = start_of_line + line.len_chars();
|
||||
} else {
|
||||
self.selection_pos = start_of_line + line.len_chars() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor_to_end_of_line(&mut self) {
|
||||
let line_idx = self.content.char_to_line(self.cursor_pos);
|
||||
let start_of_line = self.content.line_to_char(line_idx);
|
||||
|
@ -142,6 +183,14 @@ impl EditorData {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn select_to_start_of_line(&mut self) {
|
||||
let start_of_line = self
|
||||
.content
|
||||
.line_to_char(self.content.char_to_line(self.cursor_pos));
|
||||
|
||||
self.selection_pos = start_of_line;
|
||||
}
|
||||
|
||||
pub fn cursor_to_start_of_line(&mut self) {
|
||||
let start_of_line = self
|
||||
.content
|
||||
|
@ -157,23 +206,52 @@ impl EditorData {
|
|||
|
||||
pub fn select_left(&mut self) {
|
||||
if self.cursor_pos > 0 {
|
||||
self.selection_pos = self.cursor_pos - 1;
|
||||
self.selection_pos -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_right(&mut self) {
|
||||
if self.cursor_pos < self.content.len_chars() {
|
||||
self.selection_pos = self.cursor_pos + 1;
|
||||
self.selection_pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AbacusEditor;
|
||||
pub struct AbacusEditor {
|
||||
syntax_set: SyntaxSet,
|
||||
theme_set: ThemeSet,
|
||||
}
|
||||
|
||||
impl Default for AbacusEditor {
|
||||
fn default() -> Self {
|
||||
let syntax_set = SyntaxSet::load_defaults_newlines();
|
||||
let theme_set = ThemeSet::load_defaults();
|
||||
|
||||
Self {
|
||||
syntax_set,
|
||||
theme_set,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AbacusEditor {
|
||||
fn paint_cursor(&self, ctx: &mut PaintCtx, data: &EditorData, layout: &CairoTextLayout) {
|
||||
dbg!(data.cursor_pos);
|
||||
if data.mode == EditMode::Insert {
|
||||
if let Some(char_rect) = layout
|
||||
if data.cursor_pos == 0 {
|
||||
let rects = layout.rects_for_range(0..1);
|
||||
let rect = rects.first().unwrap();
|
||||
let rect = Rect::new(
|
||||
rect.min_x() + 1.0,
|
||||
rect.min_y() + 2.0,
|
||||
rect.min_x() + 3.0,
|
||||
rect.max_y() - 2.0,
|
||||
);
|
||||
ctx.fill(
|
||||
rect,
|
||||
&Color::rgba8(255, 255, 255, data.cursor_opactiy.floor() as u8),
|
||||
);
|
||||
} else if let Some(char_rect) = layout
|
||||
.rects_for_range((data.cursor_pos.max(1) - 1)..data.cursor_pos)
|
||||
.last()
|
||||
{
|
||||
|
@ -192,10 +270,8 @@ impl AbacusEditor {
|
|||
}
|
||||
|
||||
if data.mode == EditMode::Normal {
|
||||
if let Some(char_rect) = layout
|
||||
.rects_for_range(data.cursor_pos.max(1)..data.cursor_pos + 1)
|
||||
.last()
|
||||
{
|
||||
let c_pos = data.cursor_pos.min(data.content.len_chars() - 1);
|
||||
if let Some(char_rect) = layout.rects_for_range(c_pos..(c_pos + 1)).last() {
|
||||
if char_rect.width() == 0. {
|
||||
let rect = Rect::new(
|
||||
char_rect.min_x(),
|
||||
|
@ -222,20 +298,18 @@ impl AbacusEditor {
|
|||
ctx: &mut PaintCtx,
|
||||
data: &EditorData,
|
||||
) -> CairoTextLayout {
|
||||
let ps = SyntaxSet::load_defaults_newlines();
|
||||
let ts = ThemeSet::load_defaults();
|
||||
let syntax = ps.find_syntax_by_extension("rs").unwrap();
|
||||
let mut h = HighlightLines::new(syntax, &ts.themes["base16-mocha.dark"]);
|
||||
let syntax = self.syntax_set.find_syntax_by_extension("rs").unwrap();
|
||||
let mut h = HighlightLines::new(syntax, &self.theme_set.themes["base16-mocha.dark"]);
|
||||
|
||||
let mut layout = ctx
|
||||
.text()
|
||||
.new_text_layout(data.content.to_string())
|
||||
.font(FontFamily::MONOSPACE, 22.0);
|
||||
.font(FontFamily::MONOSPACE, FONT_SIZE);
|
||||
|
||||
let mut pos = 0;
|
||||
for line in data.content.lines() {
|
||||
let s = line.to_string();
|
||||
let ranges: Vec<(Style, &str)> = h.highlight_line(&s, &ps).unwrap();
|
||||
let ranges: Vec<(Style, &str)> = h.highlight_line(&s, &self.syntax_set).unwrap();
|
||||
|
||||
for (style, txt) in ranges {
|
||||
layout = layout.range_attribute(
|
||||
|
@ -257,21 +331,24 @@ impl AbacusEditor {
|
|||
|
||||
impl Widget<EditorData> for AbacusEditor {
|
||||
fn paint(&mut self, ctx: &mut druid::PaintCtx, data: &EditorData, _env: &druid::Env) {
|
||||
let size = ctx.size();
|
||||
let rect = size.to_rect();
|
||||
println!("test");
|
||||
// let size = ctx.size();
|
||||
// let rect = size.to_rect();
|
||||
|
||||
if ctx.is_focused() {
|
||||
ctx.fill(rect, &Color::rgb8(10, 10, 10));
|
||||
} else {
|
||||
ctx.fill(rect, &Color::rgb8(20, 20, 20));
|
||||
}
|
||||
// if ctx.is_focused() {
|
||||
// ctx.fill(rect, &Color::rgb8(30, 30, 30));
|
||||
// } else {
|
||||
// ctx.fill(rect, &Color::rgb8(20, 20, 20));
|
||||
// }
|
||||
|
||||
if data.content.len_chars() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let layout = self.build_highlighted_layout(ctx, data);
|
||||
self.paint_cursor(ctx, data, &layout);
|
||||
if ctx.has_focus() {
|
||||
self.paint_cursor(ctx, data, &layout);
|
||||
}
|
||||
|
||||
if data.selection_pos != data.cursor_pos {
|
||||
let rects = layout.rects_for_range(data.select_range());
|
||||
|
@ -291,139 +368,189 @@ impl Widget<EditorData> for AbacusEditor {
|
|||
_env: &druid::Env,
|
||||
) {
|
||||
match event {
|
||||
Event::KeyDown(e) => match &e.key {
|
||||
druid::keyboard_types::Key::Character(ch)
|
||||
if data.mode == EditMode::Insert && e.mods.ctrl() =>
|
||||
{
|
||||
match ch.as_ref() {
|
||||
"a" => {
|
||||
if e.mods.ctrl() {
|
||||
data.select_all();
|
||||
Event::KeyDown(e) => {
|
||||
match &e.key {
|
||||
druid::keyboard_types::Key::Character(ch) if e.mods.ctrl() => {
|
||||
match ch.as_ref() {
|
||||
"a" => {
|
||||
if e.mods.ctrl() {
|
||||
data.select_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
"u" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
druid::keyboard_types::Key::Character(c) if data.mode == EditMode::Insert => {
|
||||
data.push_str(c);
|
||||
ctx.request_paint();
|
||||
}
|
||||
druid::keyboard_types::Key::Character(ch) if data.mode == EditMode::Normal => {
|
||||
match ch.as_ref() {
|
||||
"i" => {
|
||||
data.mode = EditMode::Insert;
|
||||
}
|
||||
"A" => {
|
||||
data.mode = EditMode::Insert;
|
||||
data.cursor_to_end_of_line();
|
||||
}
|
||||
"a" => {
|
||||
if e.mods.ctrl() {
|
||||
data.select_all();
|
||||
"c" => {
|
||||
if data.cursor_pos != data.selection_pos {
|
||||
let mut cb: clipboard::ClipboardContext =
|
||||
clipboard::ClipboardProvider::new().unwrap();
|
||||
if let Some(slice) = data.content.get_slice(data.select_range())
|
||||
{
|
||||
let _ = cb.set_contents(slice.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
"v" => {
|
||||
let mut cb: clipboard::ClipboardContext =
|
||||
clipboard::ClipboardProvider::new().unwrap();
|
||||
data.push_str(&cb.get_contents().unwrap_or_default());
|
||||
ctx.request_paint();
|
||||
ctx.request_layout();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
"h" => {
|
||||
data.cursor_left();
|
||||
}
|
||||
"j" => {
|
||||
data.cursor_down();
|
||||
}
|
||||
"k" => {
|
||||
data.cursor_up();
|
||||
}
|
||||
"l" => {
|
||||
data.cursor_right();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
druid::keyboard_types::Key::Enter => {
|
||||
data.push('\n');
|
||||
ctx.request_paint();
|
||||
ctx.request_layout();
|
||||
}
|
||||
druid::keyboard_types::Key::Backspace => {
|
||||
data.delete_char_back();
|
||||
ctx.request_paint();
|
||||
}
|
||||
druid::keyboard_types::Key::Escape => {
|
||||
data.mode = EditMode::Normal;
|
||||
}
|
||||
druid::keyboard_types::Key::Character(c) if data.mode == EditMode::Insert => {
|
||||
data.push_str(c);
|
||||
ctx.request_paint();
|
||||
}
|
||||
druid::keyboard_types::Key::Character(ch) if data.mode == EditMode::Normal => {
|
||||
match ch.as_ref() {
|
||||
"i" => {
|
||||
data.mode = EditMode::Insert;
|
||||
}
|
||||
"O" => {
|
||||
data.mode = EditMode::Insert;
|
||||
data.cursor_to_start_of_line();
|
||||
data.content.insert(data.cursor_pos, "\n");
|
||||
}
|
||||
"A" => {
|
||||
data.mode = EditMode::Insert;
|
||||
data.cursor_to_end_of_line();
|
||||
}
|
||||
"a" => {
|
||||
if e.mods.ctrl() {
|
||||
data.select_all();
|
||||
}
|
||||
}
|
||||
"h" => {
|
||||
data.cursor_left();
|
||||
}
|
||||
"j" => {
|
||||
data.cursor_down();
|
||||
}
|
||||
"k" => {
|
||||
data.cursor_up();
|
||||
}
|
||||
"l" => {
|
||||
data.cursor_right();
|
||||
}
|
||||
"x" => {
|
||||
data.delete_char_forward();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
druid::keyboard_types::Key::Enter if e.mods.ctrl() => {
|
||||
ctx.submit_command(crate::commands::PROCESS_WORKBOOK.to(Target::Global));
|
||||
}
|
||||
druid::keyboard_types::Key::Enter => {
|
||||
data.push('\n');
|
||||
ctx.request_layout();
|
||||
ctx.request_paint();
|
||||
}
|
||||
druid::keyboard_types::Key::Backspace => {
|
||||
data.delete_char_back();
|
||||
ctx.request_layout();
|
||||
}
|
||||
druid::keyboard_types::Key::Delete => {
|
||||
data.delete_char_forward();
|
||||
ctx.request_layout();
|
||||
}
|
||||
druid::keyboard_types::Key::Escape => {
|
||||
data.mode = EditMode::Normal;
|
||||
}
|
||||
|
||||
druid::keyboard_types::Key::ArrowLeft if e.mods.shift() => {
|
||||
data.select_left();
|
||||
druid::keyboard_types::Key::ArrowLeft if e.mods.shift() => {
|
||||
data.select_left();
|
||||
}
|
||||
druid::keyboard_types::Key::ArrowRight if e.mods.shift() => {
|
||||
data.select_right();
|
||||
}
|
||||
druid::keyboard_types::Key::ArrowLeft if !e.mods.shift() => {
|
||||
data.cursor_left();
|
||||
}
|
||||
druid::keyboard_types::Key::ArrowRight if !e.mods.shift() => {
|
||||
data.cursor_right();
|
||||
}
|
||||
druid::keyboard_types::Key::ArrowUp if !e.mods.shift() => {
|
||||
data.cursor_up();
|
||||
}
|
||||
druid::keyboard_types::Key::ArrowDown if !e.mods.shift() => {
|
||||
data.cursor_down();
|
||||
}
|
||||
druid::keyboard_types::Key::Tab => {
|
||||
data.push_str(" ");
|
||||
}
|
||||
druid::keyboard_types::Key::End if e.mods.shift() => {
|
||||
data.select_to_end_of_line();
|
||||
}
|
||||
druid::keyboard_types::Key::Home if e.mods.shift() => {
|
||||
data.select_to_start_of_line();
|
||||
}
|
||||
druid::keyboard_types::Key::End => {
|
||||
data.cursor_to_end_of_line();
|
||||
data.deselect();
|
||||
}
|
||||
druid::keyboard_types::Key::Home => {
|
||||
data.cursor_to_start_of_line();
|
||||
data.deselect();
|
||||
}
|
||||
e => {
|
||||
dbg!(e);
|
||||
}
|
||||
}
|
||||
druid::keyboard_types::Key::ArrowLeft if !e.mods.shift() => {
|
||||
data.cursor_left();
|
||||
}
|
||||
druid::keyboard_types::Key::ArrowRight if !e.mods.shift() => {
|
||||
data.cursor_right();
|
||||
}
|
||||
druid::keyboard_types::Key::ArrowUp if !e.mods.shift() => {
|
||||
data.cursor_up();
|
||||
}
|
||||
druid::keyboard_types::Key::ArrowDown if !e.mods.shift() => {
|
||||
data.cursor_down();
|
||||
}
|
||||
druid::keyboard_types::Key::Tab => {
|
||||
data.push_str(" ");
|
||||
ctx.request_paint();
|
||||
}
|
||||
druid::keyboard_types::Key::End => {
|
||||
data.cursor_to_end_of_line();
|
||||
data.deselect();
|
||||
}
|
||||
druid::keyboard_types::Key::Home => {
|
||||
data.cursor_to_start_of_line();
|
||||
data.deselect();
|
||||
}
|
||||
e => {
|
||||
dbg!(e);
|
||||
}
|
||||
},
|
||||
ctx.request_paint();
|
||||
}
|
||||
Event::MouseDown(e) => {
|
||||
if !ctx.is_focused() {
|
||||
ctx.request_focus();
|
||||
}
|
||||
|
||||
let layout = ctx
|
||||
.text()
|
||||
.new_text_layout(data.content.to_string())
|
||||
.font(FontFamily::MONOSPACE, 24.0)
|
||||
.text_color(Color::rgb8(255, 255, 255))
|
||||
.font(FontFamily::MONOSPACE, FONT_SIZE)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let pos = layout.hit_test_point(e.pos);
|
||||
data.cursor_pos = pos.idx;
|
||||
data.selection_pos = pos.idx;
|
||||
if pos.idx != data.cursor_pos {
|
||||
data.cursor_pos = pos.idx;
|
||||
data.deselect();
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
Event::MouseMove(e) => {
|
||||
let layout = ctx
|
||||
.text()
|
||||
.new_text_layout(data.content.to_string())
|
||||
.font(FontFamily::MONOSPACE, 24.0)
|
||||
.font(FontFamily::MONOSPACE, FONT_SIZE)
|
||||
.text_color(Color::rgb8(255, 255, 255))
|
||||
.build()
|
||||
.unwrap();
|
||||
let pos = layout.hit_test_point(e.pos);
|
||||
if e.buttons.has_left() {
|
||||
data.selection_pos = pos.idx;
|
||||
let new_pos = (pos.idx + 1).min(data.content.len_chars());
|
||||
if new_pos != data.selection_pos {
|
||||
if new_pos > data.cursor_pos {
|
||||
data.selection_pos = (pos.idx + 1).min(data.content.len_chars());
|
||||
} else {
|
||||
data.selection_pos = pos.idx.max(1) - 1;
|
||||
}
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::AnimFrame(e) => {
|
||||
data.cursor_opactiy += ((*e as f64) * 0.00000065) * data.cursor_fade;
|
||||
if data.cursor_opactiy >= 255.0 {
|
||||
data.cursor_opactiy = 255.0;
|
||||
data.cursor_fade *= -1.0;
|
||||
} else if data.cursor_opactiy <= 0.0 {
|
||||
data.cursor_opactiy = 0.1;
|
||||
data.cursor_fade *= -1.0;
|
||||
}
|
||||
ctx.request_paint();
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
// Event::AnimFrame(e) => {
|
||||
// data.cursor_opactiy += ((*e as f64) * 0.00000065) * data.cursor_fade;
|
||||
// if data.cursor_opactiy >= 255.0 {
|
||||
// data.cursor_opactiy = 255.0;
|
||||
// data.cursor_fade *= -1.0;
|
||||
// } else if data.cursor_opactiy <= 0.0 {
|
||||
// data.cursor_opactiy = 0.1;
|
||||
// data.cursor_fade *= -1.0;
|
||||
// }
|
||||
// if ctx.has_focus() {
|
||||
// ctx.request_paint();
|
||||
// ctx.request_anim_frame();
|
||||
// }
|
||||
// }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -438,7 +565,6 @@ impl Widget<EditorData> for AbacusEditor {
|
|||
match event {
|
||||
LifeCycle::FocusChanged(_) => {
|
||||
ctx.request_paint();
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
LifeCycle::WidgetAdded => {
|
||||
// ctx.register_text_input(document)
|
||||
|
@ -452,11 +578,15 @@ impl Widget<EditorData> for AbacusEditor {
|
|||
|
||||
fn update(
|
||||
&mut self,
|
||||
_ctx: &mut druid::UpdateCtx,
|
||||
_old_data: &EditorData,
|
||||
_data: &EditorData,
|
||||
ctx: &mut druid::UpdateCtx,
|
||||
old_data: &EditorData,
|
||||
data: &EditorData,
|
||||
_env: &druid::Env,
|
||||
) {
|
||||
if old_data != data {
|
||||
ctx.request_paint();
|
||||
ctx.request_layout();
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
@ -469,11 +599,69 @@ impl Widget<EditorData> for AbacusEditor {
|
|||
let layout = ctx
|
||||
.text()
|
||||
.new_text_layout(data.content.to_string())
|
||||
.font(FontFamily::MONOSPACE, 22.0)
|
||||
.font(FontFamily::MONOSPACE, FONT_SIZE)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
bc.shrink_max_height_to(layout.size().height);
|
||||
bc.max()
|
||||
(bc.max().width, layout.size().height).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn editor_header() -> impl Widget<Block> {
|
||||
let ban_svg = include_str!("../assets/ban.svg")
|
||||
.parse::<SvgData>()
|
||||
.unwrap_or_default();
|
||||
let trash_svg = include_str!("../assets/trash.svg")
|
||||
.parse::<SvgData>()
|
||||
.unwrap_or_default();
|
||||
let run_svg = include_str!("../assets/play.svg")
|
||||
.parse::<SvgData>()
|
||||
.unwrap_or_default();
|
||||
|
||||
Container::new(
|
||||
Flex::row()
|
||||
.must_fill_main_axis(true)
|
||||
.with_spacer(20.0)
|
||||
.with_child(
|
||||
Label::dynamic(|data: &Block, _| data.name.clone())
|
||||
.with_font(
|
||||
FontDescriptor::new(FontFamily::SANS_SERIF)
|
||||
.with_weight(FontWeight::BOLD)
|
||||
.with_size(14.0),
|
||||
)
|
||||
.padding(5.0),
|
||||
)
|
||||
.with_flex_spacer(1.0)
|
||||
.with_child(
|
||||
Container::new(Padding::new(10.0, Svg::new(run_svg).fix_width(10.0)))
|
||||
.controller(ToolbarButtonController::new(Color::rgb8(50, 50, 50)))
|
||||
.on_click(|ctx, data: &mut Block, _env| {
|
||||
ctx.submit_command(
|
||||
crate::commands::PROCESS_BLOCK
|
||||
.with(data.index)
|
||||
.to(Target::Global),
|
||||
);
|
||||
}),
|
||||
)
|
||||
.with_spacer(10.0)
|
||||
.with_child(
|
||||
Container::new(Padding::new(10.0, Svg::new(ban_svg).fix_width(10.0)))
|
||||
.controller(ToolbarButtonController::new(Color::rgb8(50, 50, 50)))
|
||||
.on_click(|_ctx, data: &mut Block, _env| data.output = Output::None),
|
||||
)
|
||||
.with_spacer(10.0)
|
||||
.with_child(
|
||||
Container::new(Padding::new(10.0, Svg::new(trash_svg).fix_width(10.0)))
|
||||
.controller(ToolbarButtonController::new(Color::rgb8(50, 50, 50)))
|
||||
.on_click(|ctx, data: &mut Block, _env| {
|
||||
ctx.submit_command(
|
||||
crate::commands::DELETE_BLOCK
|
||||
.with(data.index)
|
||||
.to(Target::Global),
|
||||
);
|
||||
}),
|
||||
)
|
||||
.with_spacer(20.0),
|
||||
)
|
||||
.background(Color::rgb8(35, 35, 35))
|
||||
.fix_height(30.0)
|
||||
}
|
||||
|
|
|
@ -1,23 +1,43 @@
|
|||
mod app_delegate;
|
||||
mod app_header;
|
||||
mod commands;
|
||||
mod data;
|
||||
mod editor;
|
||||
mod output_block;
|
||||
|
||||
use druid::widget::{Flex, Padding};
|
||||
use druid::{AppLauncher, Data, PlatformError, Widget, WindowDesc};
|
||||
use data::{AppData, Block};
|
||||
use druid::widget::{Container, Flex, List, Padding, Scroll};
|
||||
use druid::{AppLauncher, PlatformError, Widget, WidgetExt, WindowDesc};
|
||||
|
||||
fn build_ui() -> impl Widget<editor::EditorData> {
|
||||
Padding::new(
|
||||
10.0,
|
||||
Flex::column()
|
||||
.with_flex_child(editor::AbacusEditor, 2.0)
|
||||
.with_flex_child(editor::AbacusEditor, 1.0),
|
||||
)
|
||||
fn build_ui() -> impl Widget<AppData> {
|
||||
Flex::column()
|
||||
.with_child(app_header::app_header_ui())
|
||||
.with_child(app_header::header_separater())
|
||||
.with_flex_child(
|
||||
Scroll::new(
|
||||
List::new(|| {
|
||||
Flex::column()
|
||||
.with_child(editor::editor_header())
|
||||
.with_child(Container::new(Padding::new(
|
||||
10.0,
|
||||
editor::AbacusEditor::default().lens(Block::editor_data),
|
||||
)))
|
||||
.with_child(output_block::output_block())
|
||||
})
|
||||
.lens(AppData::blocks),
|
||||
)
|
||||
.vertical(),
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), PlatformError> {
|
||||
AppLauncher::with_window(WindowDesc::new(build_ui())).launch(editor::EditorData::default())?;
|
||||
AppLauncher::with_window(
|
||||
WindowDesc::new(build_ui())
|
||||
.resizable(true)
|
||||
.window_size((600.0, 800.0)),
|
||||
)
|
||||
.delegate(app_delegate::Delegate)
|
||||
.launch(AppData::default())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Data)]
|
||||
struct CodeEditor {
|
||||
code: String,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
use abacus_core::Output;
|
||||
use druid::{
|
||||
widget::{Flex, Label, Padding, ViewSwitcher},
|
||||
Color, FontDescriptor, FontFamily, FontWeight, Widget, WidgetExt,
|
||||
};
|
||||
|
||||
use crate::data::Block;
|
||||
|
||||
const OUTPUT_FONT_SIZE: f64 = 16.0;
|
||||
|
||||
pub fn output_block() -> impl Widget<Block> {
|
||||
ViewSwitcher::new(
|
||||
|data: &Block, _env| data.clone(),
|
||||
|selector: &Block, _data, _env| match &selector.output {
|
||||
Output::Scalar(v) => Box::new(Padding::new(
|
||||
25.0,
|
||||
Label::new(v.to_string())
|
||||
.with_font(
|
||||
FontDescriptor::new(FontFamily::MONOSPACE).with_weight(FontWeight::BOLD),
|
||||
)
|
||||
.with_text_size(OUTPUT_FONT_SIZE)
|
||||
.padding(10.0)
|
||||
.background(Color::rgb8(30, 30, 30))
|
||||
.rounded(4.0)
|
||||
.expand_width(),
|
||||
)),
|
||||
Output::Error(e) => Box::new(Padding::new(
|
||||
25.0,
|
||||
Label::new(e.to_string())
|
||||
.with_font(
|
||||
FontDescriptor::new(FontFamily::MONOSPACE).with_weight(FontWeight::BOLD),
|
||||
)
|
||||
.with_text_alignment(druid::TextAlignment::Center)
|
||||
.with_line_break_mode(druid::widget::LineBreaking::WordWrap)
|
||||
.with_text_size(OUTPUT_FONT_SIZE)
|
||||
.padding(10.0)
|
||||
.background(Color::rgb8(30, 10, 10))
|
||||
.expand_width(),
|
||||
)),
|
||||
Output::Series(series) => {
|
||||
let mut flex = Flex::row();
|
||||
|
||||
for v in series.iter() {
|
||||
flex.add_child(
|
||||
Label::new(v.to_string())
|
||||
.with_font(
|
||||
FontDescriptor::new(FontFamily::MONOSPACE)
|
||||
.with_weight(FontWeight::BOLD),
|
||||
)
|
||||
.with_text_size(OUTPUT_FONT_SIZE)
|
||||
.padding(3.0)
|
||||
.border(Color::rgb8(60, 60, 60), 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
Box::new(Padding::new(
|
||||
25.0,
|
||||
flex.padding(10.0)
|
||||
.background(Color::rgb8(30, 30, 30))
|
||||
.rounded(4.0)
|
||||
.expand_width(),
|
||||
))
|
||||
}
|
||||
_ => Box::new(Padding::new(
|
||||
0.0,
|
||||
Label::new("")
|
||||
.with_font(
|
||||
FontDescriptor::new(FontFamily::MONOSPACE).with_weight(FontWeight::BOLD),
|
||||
)
|
||||
.with_text_size(0.0)
|
||||
.padding(0.0)
|
||||
.background(Color::TRANSPARENT),
|
||||
)),
|
||||
},
|
||||
)
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
use druid::{
|
||||
kurbo::Line,
|
||||
widget::{FillStrat, SvgData},
|
||||
Color, Data, Rect, RenderContext, Widget,
|
||||
};
|
||||
|
||||
pub struct ToolbarItem {
|
||||
icon: SvgData,
|
||||
}
|
||||
|
||||
impl<T: Data> Widget<T> for ToolbarItem {
|
||||
fn event(
|
||||
&mut self,
|
||||
_ctx: &mut druid::EventCtx,
|
||||
_event: &druid::Event,
|
||||
_data: &mut T,
|
||||
_env: &druid::Env,
|
||||
) {
|
||||
}
|
||||
|
||||
fn lifecycle(
|
||||
&mut self,
|
||||
ctx: &mut druid::LifeCycleCtx,
|
||||
event: &druid::LifeCycle,
|
||||
_data: &T,
|
||||
_env: &druid::Env,
|
||||
) {
|
||||
if let druid::LifeCycle::HotChanged(true) = event {
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _ctx: &mut druid::UpdateCtx, _old_data: &T, _data: &T, _env: &druid::Env) {
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_ctx: &mut druid::LayoutCtx,
|
||||
bc: &druid::BoxConstraints,
|
||||
_data: &T,
|
||||
_env: &druid::Env,
|
||||
) -> druid::Size {
|
||||
druid::Size::new(bc.max().height * 1.6, bc.max().height)
|
||||
}
|
||||
|
||||
fn paint(&mut self, ctx: &mut druid::PaintCtx, _data: &T, _env: &druid::Env) {
|
||||
let bg_rect = Rect::ZERO.with_size(ctx.size());
|
||||
let icon_rect = Rect::from_center_size(bg_rect.center(), (15.0, 15.0));
|
||||
let offset_matrix = FillStrat::default().affine_to_fill(icon_rect.size(), self.icon.size());
|
||||
|
||||
if ctx.is_hot() {
|
||||
ctx.fill(bg_rect, &Color::rgba8(50, 50, 50, 255));
|
||||
ctx.stroke(
|
||||
Line::new(
|
||||
(0.0, bg_rect.height() - 2.0),
|
||||
(bg_rect.width(), bg_rect.height() - 2.0),
|
||||
),
|
||||
&Color::rgb8(100, 100, 100),
|
||||
2.0,
|
||||
);
|
||||
}
|
||||
|
||||
ctx.with_child_ctx(icon_rect, |ctx| {
|
||||
ctx.transform(druid::Affine::translate((icon_rect.x0, icon_rect.y0)));
|
||||
self.icon.to_piet(offset_matrix, ctx);
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue