sync commit

This commit is contained in:
Joe Bellus 2022-10-13 14:39:48 -04:00
parent 827af45ab9
commit b0d45a5eec
21 changed files with 1491 additions and 165 deletions

303
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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>() {

View File

@ -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 {

View File

@ -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>>;

View File

@ -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)
}
}

View File

@ -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"

38
abacus-ui/assets/ban.svg Normal file
View File

@ -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

View File

@ -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

View File

@ -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

38
abacus-ui/assets/play.svg Normal file
View File

@ -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

38
abacus-ui/assets/plus.svg Normal file
View File

@ -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

View File

@ -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

View File

@ -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)
}
}

176
abacus-ui/src/app_header.rs Normal file
View File

@ -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)
}
}

View File

@ -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");

71
abacus-ui/src/data.rs Normal file
View File

@ -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,
}
}
}

View File

@ -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)
}

View File

@ -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,
}

View File

@ -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),
)),
},
)
}

View File

@ -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);
});
}
}