Sync commit
This commit is contained in:
parent
4124f6dd88
commit
1acc998d7a
|
@ -14,6 +14,9 @@ pub fn setup_engine(engine: &mut rhai::Engine) {
|
||||||
engine.register_fn("dataframe", script_functions::dataframe);
|
engine.register_fn("dataframe", script_functions::dataframe);
|
||||||
engine.register_fn("select", DataFrame::s_select);
|
engine.register_fn("select", DataFrame::s_select);
|
||||||
engine.register_fn("load_csv", DataFrame::load_csv);
|
engine.register_fn("load_csv", DataFrame::load_csv);
|
||||||
|
engine.register_fn("column", DataFrame::s_column);
|
||||||
|
engine.register_indexer_get(DataFrame::s_column);
|
||||||
|
engine.register_indexer_set(DataFrame::append_column);
|
||||||
// polar series
|
// polar series
|
||||||
engine.register_type::<Series>();
|
engine.register_type::<Series>();
|
||||||
engine.register_indexer_get(Series::s_get);
|
engine.register_indexer_get(Series::s_get);
|
||||||
|
@ -22,15 +25,25 @@ pub fn setup_engine(engine: &mut rhai::Engine) {
|
||||||
engine.register_fn("to_series", script_functions::to_series_unnamed);
|
engine.register_fn("to_series", script_functions::to_series_unnamed);
|
||||||
engine.register_fn("series", script_functions::series_unnamed);
|
engine.register_fn("series", script_functions::series_unnamed);
|
||||||
engine.register_fn("head", Series::s_head);
|
engine.register_fn("head", Series::s_head);
|
||||||
engine.register_fn("+", Series::add);
|
|
||||||
engine.register_fn("sort", Series::s_sort);
|
engine.register_fn("sort", Series::s_sort);
|
||||||
engine.register_fn("sum", Series::s_sum);
|
engine.register_fn("sum", Series::s_sum);
|
||||||
engine.register_fn("add", Series::s_op_add);
|
engine.register_fn("add", Series::s_op_add);
|
||||||
engine.register_fn("column", DataFrame::s_column);
|
|
||||||
|
engine.register_fn("+", Series::add);
|
||||||
engine.register_fn("+", script_functions::add_series_i64);
|
engine.register_fn("+", script_functions::add_series_i64);
|
||||||
engine.register_fn("+", script_functions::add_series_f64);
|
engine.register_fn("+", script_functions::add_series_f64);
|
||||||
|
|
||||||
|
engine.register_fn("-", script_functions::subtract_series_series);
|
||||||
engine.register_fn("-", script_functions::subtract_series_i64);
|
engine.register_fn("-", script_functions::subtract_series_i64);
|
||||||
engine.register_fn("-", script_functions::subtract_series_f64);
|
engine.register_fn("-", script_functions::subtract_series_f64);
|
||||||
|
|
||||||
|
engine.register_fn("*", script_functions::multiply_series_series);
|
||||||
|
engine.register_fn("*", script_functions::multiply_series_i64);
|
||||||
|
engine.register_fn("*", script_functions::multiply_series_f64);
|
||||||
|
|
||||||
|
engine.register_fn("/", script_functions::div_series_series);
|
||||||
|
engine.register_fn("/", script_functions::div_series_i64);
|
||||||
|
engine.register_fn("/", script_functions::div_series_f64);
|
||||||
// polar expressions
|
// polar expressions
|
||||||
engine.register_type::<DataFrameExpression>();
|
engine.register_type::<DataFrameExpression>();
|
||||||
engine.register_fn("sum", DataFrameExpression::sum);
|
engine.register_fn("sum", DataFrameExpression::sum);
|
||||||
|
@ -54,6 +67,7 @@ pub fn setup_engine(engine: &mut rhai::Engine) {
|
||||||
engine.register_fn("first", script_functions::first);
|
engine.register_fn("first", script_functions::first);
|
||||||
let _ = engine.register_custom_operator("gt", 200);
|
let _ = engine.register_custom_operator("gt", 200);
|
||||||
let _ = engine.register_custom_operator("gte", 200);
|
let _ = engine.register_custom_operator("gte", 200);
|
||||||
|
let _ = engine.register_custom_operator("<<", 200);
|
||||||
engine.register_fn("gt", script_functions::gt_op);
|
engine.register_fn("gt", script_functions::gt_op);
|
||||||
engine.register_fn("gte", script_functions::gte_op);
|
engine.register_fn("gte", script_functions::gte_op);
|
||||||
|
|
||||||
|
@ -110,6 +124,12 @@ impl DataFrame {
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn append_column(&mut self, idx: &str, mut b: Series) -> ScriptResult<()> {
|
||||||
|
b.0.rename(idx);
|
||||||
|
self.0.with_column(b.0).map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -259,6 +279,34 @@ mod script_functions {
|
||||||
Series(a.0 - b)
|
Series(a.0 - b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subtract_series_series(a: Series, b: Series) -> Series {
|
||||||
|
Series(a.0 - b.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn multiply_series_i64(a: Series, b: i64) -> Series {
|
||||||
|
Series(a.0 * b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn multiply_series_f64(a: Series, b: f64) -> Series {
|
||||||
|
Series(a.0 * b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn multiply_series_series(a: Series, b: Series) -> Series {
|
||||||
|
Series(a.0 * b.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn div_series_i64(a: Series, b: i64) -> Series {
|
||||||
|
Series(a.0 / b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn div_series_f64(a: Series, b: f64) -> Series {
|
||||||
|
Series(a.0 / b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn div_series_series(a: Series, b: Series) -> Series {
|
||||||
|
Series(a.0 / b.0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn column(name: &str) -> DataFrameExpression {
|
pub fn column(name: &str) -> DataFrameExpression {
|
||||||
DataFrameExpression(polars::prelude::col(name))
|
DataFrameExpression(polars::prelude::col(name))
|
||||||
}
|
}
|
||||||
|
@ -326,21 +374,21 @@ mod script_functions {
|
||||||
polars::series::Series::new(
|
polars::series::Series::new(
|
||||||
name,
|
name,
|
||||||
arr.into_iter()
|
arr.into_iter()
|
||||||
.map(|i| i.cast::<i64>())
|
.filter_map(|i| i.try_cast::<i64>())
|
||||||
.collect::<Vec<i64>>(),
|
.collect::<Vec<i64>>(),
|
||||||
)
|
)
|
||||||
} else if i.type_id() == TypeId::of::<f64>() {
|
} else if i.type_id() == TypeId::of::<f64>() {
|
||||||
polars::series::Series::new(
|
polars::series::Series::new(
|
||||||
name,
|
name,
|
||||||
arr.into_iter()
|
arr.into_iter()
|
||||||
.map(|i| i.cast::<f64>())
|
.filter_map(|i| i.try_cast::<f64>())
|
||||||
.collect::<Vec<f64>>(),
|
.collect::<Vec<f64>>(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
polars::series::Series::new(
|
polars::series::Series::new(
|
||||||
name,
|
name,
|
||||||
arr.into_iter()
|
arr.into_iter()
|
||||||
.map(|i| i.cast::<String>())
|
.filter_map(|i| i.try_cast::<String>())
|
||||||
.collect::<Vec<String>>(),
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -412,12 +460,20 @@ fn implementation_df_select(
|
||||||
|
|
||||||
let select_expressions = select_array
|
let select_expressions = select_array
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| {
|
.filter_map(|s| {
|
||||||
filter_array
|
let select_expr = if s.is::<String>() {
|
||||||
.iter()
|
polars::prelude::col(&s.to_string())
|
||||||
.fold(polars::prelude::col(&s.to_string()), |acc, i| {
|
} else if s.is::<DataFrameExpression>() {
|
||||||
acc.filter(i.0.clone())
|
s.clone().cast::<DataFrameExpression>().0
|
||||||
})
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(
|
||||||
|
filter_array
|
||||||
|
.iter()
|
||||||
|
.fold(select_expr, |acc, i| acc.filter(i.0.clone())),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::dataframe;
|
use crate::dataframe;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Engine {
|
pub struct Engine<'a> {
|
||||||
pub blocks: Vec<Block>,
|
|
||||||
engine: rhai::Engine,
|
engine: rhai::Engine,
|
||||||
|
scope: rhai::Scope<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Engine {
|
impl<'a> Default for Engine<'a> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let mut engine = rhai::Engine::new();
|
let mut engine = rhai::Engine::new();
|
||||||
engine.set_fast_operators(false);
|
engine.set_fast_operators(false);
|
||||||
|
@ -14,42 +14,24 @@ impl Default for Engine {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
blocks: vec![Block::default()],
|
scope: rhai::Scope::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl<'a> Engine<'a> {
|
||||||
pub fn process(&mut self) {
|
|
||||||
for block in self.blocks.iter_mut() {
|
|
||||||
match self.engine.eval::<rhai::Dynamic>(&block.script) {
|
|
||||||
Ok(res) if res.is::<dataframe::DataFrame>() => {
|
|
||||||
let frame = rhai::Dynamic::cast::<dataframe::DataFrame>(res);
|
|
||||||
block.output = Output::DataFrame(frame);
|
|
||||||
}
|
|
||||||
Ok(res) if res.is::<dataframe::Series>() => {
|
|
||||||
let frame = rhai::Dynamic::cast::<dataframe::Series>(res);
|
|
||||||
block.output = Output::Series(frame);
|
|
||||||
}
|
|
||||||
Ok(res) => {
|
|
||||||
block.output = Output::Scalar(res);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
block.output = Output::Error(e.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_script(&mut self, script: &str) -> Output {
|
pub fn process_script(&mut self, script: &str) -> Output {
|
||||||
match self.engine.eval::<rhai::Dynamic>(script) {
|
match self
|
||||||
|
.engine
|
||||||
|
.eval_with_scope::<rhai::Dynamic>(&mut self.scope, script)
|
||||||
|
{
|
||||||
Ok(res) if res.is::<dataframe::DataFrame>() => {
|
Ok(res) if res.is::<dataframe::DataFrame>() => {
|
||||||
let frame = rhai::Dynamic::cast::<dataframe::DataFrame>(res);
|
let frame = rhai::Dynamic::cast::<dataframe::DataFrame>(res);
|
||||||
Output::DataFrame(frame)
|
Output::DataFrame(frame)
|
||||||
}
|
}
|
||||||
Ok(res) if res.is::<dataframe::Series>() => {
|
Ok(res) if res.is::<dataframe::Series>() => {
|
||||||
let frame = rhai::Dynamic::cast::<dataframe::Series>(res);
|
let series = rhai::Dynamic::cast::<dataframe::Series>(res);
|
||||||
Output::Series(frame)
|
Output::Series(series)
|
||||||
}
|
}
|
||||||
Ok(res) => Output::Scalar(res),
|
Ok(res) => Output::Scalar(res),
|
||||||
Err(e) => Output::Error(e.to_string()),
|
Err(e) => Output::Error(e.to_string()),
|
||||||
|
@ -105,29 +87,12 @@ impl Output {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Block {
|
|
||||||
pub script: String,
|
|
||||||
pub output: Output,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Block {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
script: Default::default(),
|
|
||||||
output: Output::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn process(script: &str) -> Output {
|
pub fn process(script: &str) -> Output {
|
||||||
let mut engine = Engine::default();
|
let mut engine = Engine::default();
|
||||||
engine.blocks[0].script = script.into();
|
engine.process_script(script)
|
||||||
engine.process();
|
|
||||||
engine.blocks[0].output.clone()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ mod dataframe;
|
||||||
mod engine;
|
mod engine;
|
||||||
mod save_file;
|
mod save_file;
|
||||||
|
|
||||||
pub use engine::Block;
|
|
||||||
pub use engine::Engine;
|
pub use engine::Engine;
|
||||||
pub use engine::Output;
|
pub use engine::Output;
|
||||||
pub use save_file::{SaveBlock, SaveFile};
|
pub use save_file::{SaveBlock, SaveFile};
|
||||||
|
|
|
@ -18,15 +18,8 @@ impl AppDelegate<AppData> for Delegate {
|
||||||
) -> druid::Handled {
|
) -> druid::Handled {
|
||||||
if let Some(file_info) = cmd.get(druid::commands::SAVE_FILE_AS) {
|
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 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);
|
data.filename = Some(filepath);
|
||||||
|
data.save();
|
||||||
return druid::Handled::Yes;
|
return druid::Handled::Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,20 +49,23 @@ pub fn app_header_ui() -> impl Widget<AppData> {
|
||||||
.with_child(
|
.with_child(
|
||||||
Container::new(Padding::new(10.0, Svg::new(disk_svg).fix_width(10.0)))
|
Container::new(Padding::new(10.0, Svg::new(disk_svg).fix_width(10.0)))
|
||||||
.controller(ToolbarButtonController::new(Color::rgb8(50, 50, 50)))
|
.controller(ToolbarButtonController::new(Color::rgb8(50, 50, 50)))
|
||||||
.on_click(|ctx, _, _| {
|
.on_click(|ctx, data: &mut AppData, _| {
|
||||||
let abacus = druid::FileSpec::new("Abacus File", &["abacus"]);
|
let abacus = druid::FileSpec::new("Abacus File", &["abacus"]);
|
||||||
let json = druid::FileSpec::new("JSON File", &["json"]);
|
let json = druid::FileSpec::new("JSON File", &["json"]);
|
||||||
|
if data.filename.is_some() {
|
||||||
|
data.save();
|
||||||
|
} else {
|
||||||
|
let save_dialog_options = druid::FileDialogOptions::new()
|
||||||
|
.allowed_types(vec![abacus, json])
|
||||||
|
.default_type(abacus)
|
||||||
|
.name_label("Target")
|
||||||
|
.title("Save workbook")
|
||||||
|
.button_text("Save");
|
||||||
|
|
||||||
let save_dialog_options = druid::FileDialogOptions::new()
|
ctx.submit_command(
|
||||||
.allowed_types(vec![abacus, json])
|
druid::commands::SHOW_SAVE_PANEL.with(save_dialog_options),
|
||||||
.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_spacer(20.0)
|
||||||
|
|
|
@ -15,6 +15,19 @@ pub struct AppData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppData {
|
impl AppData {
|
||||||
|
pub fn save(&self) {
|
||||||
|
if let Some(ref filepath) = self.filename {
|
||||||
|
let _ = abacus_core::SaveFile::new(
|
||||||
|
filepath.clone(),
|
||||||
|
self.blocks
|
||||||
|
.iter()
|
||||||
|
.map(|d| abacus_core::SaveBlock::new(&d.name, &d.editor_data.content))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove_block(&mut self, idx: usize) {
|
pub fn remove_block(&mut self, idx: usize) {
|
||||||
self.blocks.remove(idx);
|
self.blocks.remove(idx);
|
||||||
for (idx, block) in self.blocks.iter_mut().enumerate() {
|
for (idx, block) in self.blocks.iter_mut().enumerate() {
|
||||||
|
@ -36,6 +49,7 @@ impl Default for AppData {
|
||||||
impl AppData {
|
impl AppData {
|
||||||
pub fn process(&mut self) {
|
pub fn process(&mut self) {
|
||||||
let mut engine = abacus_core::Engine::default();
|
let mut engine = abacus_core::Engine::default();
|
||||||
|
|
||||||
for mut block in self.blocks.iter_mut() {
|
for mut block in self.blocks.iter_mut() {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
block.output = engine.process_script(&block.editor_data.content.to_string());
|
block.output = engine.process_script(&block.editor_data.content.to_string());
|
||||||
|
|
|
@ -23,9 +23,9 @@ pub struct EditorData {
|
||||||
impl Default for EditorData {
|
impl Default for EditorData {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
content: Rope::from_str("let x = 1;\nx+1"),
|
content: Rope::from_str("1234567890\n\n\n123\n\n1234\n1234"),
|
||||||
cursor_pos: 5,
|
cursor_pos: 0,
|
||||||
selection_pos: 5,
|
selection_pos: 0,
|
||||||
mode: EditMode::Normal,
|
mode: EditMode::Normal,
|
||||||
cursor_opactiy: 255.0,
|
cursor_opactiy: 255.0,
|
||||||
cursor_fade: 1.0,
|
cursor_fade: 1.0,
|
||||||
|
@ -35,7 +35,6 @@ impl Default for EditorData {
|
||||||
|
|
||||||
impl EditorData {
|
impl EditorData {
|
||||||
pub fn new(content: &str) -> Self {
|
pub fn new(content: &str) -> Self {
|
||||||
println!("new block: {}", content);
|
|
||||||
Self {
|
Self {
|
||||||
content: Rope::from_str(content),
|
content: Rope::from_str(content),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -43,10 +42,16 @@ impl EditorData {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_cursor(&mut self, idx: usize) {
|
pub fn move_cursor(&mut self, idx: usize) {
|
||||||
|
let has_selection = self.cursor_pos != self.selection_pos;
|
||||||
if idx <= self.content.len_chars() {
|
if idx <= self.content.len_chars() {
|
||||||
self.cursor_pos = idx;
|
self.cursor_pos = idx;
|
||||||
|
}
|
||||||
|
if self.mode == EditMode::Normal && self.cursor_pos == 0 {
|
||||||
|
self.cursor_pos = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has_selection {
|
||||||
self.deselect();
|
self.deselect();
|
||||||
// dbg!(self.content.char(self.cursor_pos));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +133,18 @@ impl EditorData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_up(&mut self) {
|
||||||
|
let line_idx = self.content.char_to_line(self.cursor_pos);
|
||||||
|
|
||||||
|
if line_idx > 0 {
|
||||||
|
let start_of_current_line = self.content.line_to_char(line_idx);
|
||||||
|
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);
|
||||||
|
self.cursor_pos = up_line_start + line_pos.min(up_line.len_chars() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cursor_down(&mut self) {
|
pub fn cursor_down(&mut self) {
|
||||||
let line_idx = self.content.char_to_line(self.cursor_pos);
|
let line_idx = self.content.char_to_line(self.cursor_pos);
|
||||||
if line_idx < self.content.len_lines() - 1 {
|
if line_idx < self.content.len_lines() - 1 {
|
||||||
|
@ -136,14 +153,42 @@ impl EditorData {
|
||||||
let start_of_next_line = self.content.line_to_char(line_idx + 1);
|
let start_of_next_line = self.content.line_to_char(line_idx + 1);
|
||||||
let next_line_len = self.content.line(line_idx + 1).len_chars();
|
let next_line_len = self.content.line(line_idx + 1).len_chars();
|
||||||
|
|
||||||
self.move_cursor(start_of_next_line + line_pos.min(next_line_len));
|
let new_pos = start_of_next_line + line_pos.min(next_line_len);
|
||||||
|
|
||||||
|
if self.cursor_pos == new_pos {
|
||||||
|
self.move_cursor(new_pos + 1);
|
||||||
|
} else {
|
||||||
|
self.move_cursor(new_pos);
|
||||||
|
}
|
||||||
|
println!(" ");
|
||||||
|
dbg!(line_idx, new_pos, start_of_next_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_down(&mut self) {
|
||||||
|
//12 -> 11
|
||||||
|
let line_idx = self.content.char_to_line(self.cursor_pos);
|
||||||
|
if line_idx < self.content.len_lines() - 1 {
|
||||||
|
let start_of_current_line = self.content.line_to_char(line_idx);
|
||||||
|
let line_pos = self.cursor_pos - start_of_current_line;
|
||||||
|
let start_of_next_line = self.content.line_to_char(line_idx + 1) + 1;
|
||||||
|
let next_line_len = self.content.line(line_idx + 1).len_chars();
|
||||||
|
|
||||||
|
self.cursor_pos =
|
||||||
|
(start_of_next_line + line_pos.min(next_line_len)).min(start_of_next_line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cursor_right(&mut self) {
|
pub fn cursor_right(&mut self) {
|
||||||
|
let has_selection = self.cursor_pos != self.selection_pos;
|
||||||
|
|
||||||
if self.cursor_pos < self.content.len_chars() {
|
if self.cursor_pos < self.content.len_chars() {
|
||||||
self.move_cursor(self.cursor_pos + 1);
|
self.move_cursor(self.cursor_pos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !has_selection {
|
||||||
|
self.deselect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_to_end_of_line(&mut self) {
|
pub fn select_to_end_of_line(&mut self) {
|
||||||
|
@ -191,13 +236,72 @@ impl EditorData {
|
||||||
|
|
||||||
pub fn select_left(&mut self) {
|
pub fn select_left(&mut self) {
|
||||||
if self.cursor_pos > 0 {
|
if self.cursor_pos > 0 {
|
||||||
self.selection_pos -= 1;
|
self.cursor_pos -= 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_right(&mut self) {
|
pub fn select_right(&mut self) {
|
||||||
if self.cursor_pos < self.content.len_chars() {
|
if self.cursor_pos < self.content.len_chars() {
|
||||||
self.selection_pos += 1;
|
self.cursor_pos += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn current_line(&self) -> usize {
|
||||||
|
self.content.char_to_line(self.cursor_pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_char(&self) -> Option<char> {
|
||||||
|
self.content.get_char(self.cursor_pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cursor_left_normal_empty_line() {
|
||||||
|
let mut data = EditorData {
|
||||||
|
mode: EditMode::Normal,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
data.push_str("0123456789\n\n1234");
|
||||||
|
data.cursor_pos = 12;
|
||||||
|
data.selection_pos = 12;
|
||||||
|
data.cursor_left();
|
||||||
|
assert_eq!(data.cursor_pos, 11);
|
||||||
|
data.cursor_left();
|
||||||
|
assert_eq!(data.cursor_pos, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cursor_left_normal_double_empty_line() {
|
||||||
|
let mut data = EditorData {
|
||||||
|
mode: EditMode::Normal,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
data.push_str("0123456789\n\n\n1234");
|
||||||
|
data.cursor_pos = 14;
|
||||||
|
data.selection_pos = 14;
|
||||||
|
data.cursor_left();
|
||||||
|
assert_eq!(data.cursor_pos, 12);
|
||||||
|
data.cursor_left();
|
||||||
|
assert_eq!(data.cursor_pos, 11);
|
||||||
|
data.cursor_left();
|
||||||
|
assert_eq!(data.cursor_pos, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cursor_left_normal_end_of_line() {
|
||||||
|
let mut data = EditorData {
|
||||||
|
mode: EditMode::Normal,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
data.push_str("0123456789\n1234");
|
||||||
|
data.cursor_pos = 12;
|
||||||
|
data.selection_pos = 12;
|
||||||
|
assert_eq!(data.current_char().unwrap(), '1');
|
||||||
|
data.cursor_left();
|
||||||
|
assert_eq!(data.cursor_pos, 10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ impl Default for AbacusEditor {
|
||||||
|
|
||||||
impl AbacusEditor {
|
impl AbacusEditor {
|
||||||
fn paint_cursor(&self, ctx: &mut PaintCtx, data: &EditorData, layout: &CairoTextLayout) {
|
fn paint_cursor(&self, ctx: &mut PaintCtx, data: &EditorData, layout: &CairoTextLayout) {
|
||||||
dbg!(data.cursor_pos);
|
|
||||||
if data.mode == EditMode::Insert {
|
if data.mode == EditMode::Insert {
|
||||||
if data.cursor_pos == 0 {
|
if data.cursor_pos == 0 {
|
||||||
let rects = layout.rects_for_range(0..1);
|
let rects = layout.rects_for_range(0..1);
|
||||||
|
@ -69,8 +68,10 @@ impl AbacusEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.mode == EditMode::Normal {
|
if data.mode == EditMode::Normal {
|
||||||
let c_pos = data.cursor_pos.min(data.content.len_chars() - 1);
|
if let Some(char_rect) = layout
|
||||||
if let Some(char_rect) = layout.rects_for_range(c_pos..(c_pos + 1)).last() {
|
.rects_for_range(data.cursor_pos..(data.cursor_pos + 1))
|
||||||
|
.last()
|
||||||
|
{
|
||||||
if char_rect.width() == 0. {
|
if char_rect.width() == 0. {
|
||||||
let rect = Rect::new(
|
let rect = Rect::new(
|
||||||
char_rect.min_x(),
|
char_rect.min_x(),
|
||||||
|
@ -269,6 +270,13 @@ impl Widget<EditorData> for AbacusEditor {
|
||||||
druid::keyboard_types::Key::ArrowDown if !e.mods.shift() => {
|
druid::keyboard_types::Key::ArrowDown if !e.mods.shift() => {
|
||||||
data.cursor_down();
|
data.cursor_down();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
druid::keyboard_types::Key::ArrowUp if e.mods.shift() => {
|
||||||
|
data.select_up();
|
||||||
|
}
|
||||||
|
druid::keyboard_types::Key::ArrowDown if e.mods.shift() => {
|
||||||
|
data.select_down();
|
||||||
|
}
|
||||||
druid::keyboard_types::Key::Tab => {
|
druid::keyboard_types::Key::Tab => {
|
||||||
data.push_str(" ");
|
data.push_str(" ");
|
||||||
}
|
}
|
||||||
|
@ -322,7 +330,7 @@ impl Widget<EditorData> for AbacusEditor {
|
||||||
let new_pos = (pos.idx + 1).min(data.content.len_chars());
|
let new_pos = (pos.idx + 1).min(data.content.len_chars());
|
||||||
if new_pos != data.selection_pos {
|
if new_pos != data.selection_pos {
|
||||||
if new_pos > data.cursor_pos {
|
if new_pos > data.cursor_pos {
|
||||||
data.selection_pos = (pos.idx + 1).min(data.content.len_chars());
|
data.selection_pos = pos.idx.min(data.content.len_chars());
|
||||||
} else {
|
} else {
|
||||||
data.selection_pos = pos.idx.max(1) - 1;
|
data.selection_pos = pos.idx.max(1) - 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,8 @@ mod app_delegate;
|
||||||
mod app_header;
|
mod app_header;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod data;
|
mod data;
|
||||||
mod modal_container;
|
|
||||||
|
|
||||||
mod editor;
|
mod editor;
|
||||||
|
mod modal_container;
|
||||||
mod output_block;
|
mod output_block;
|
||||||
|
|
||||||
use data::{AppData, Block};
|
use data::{AppData, Block};
|
||||||
|
|
|
@ -39,9 +39,7 @@ impl Widget<data::AppData> for ModalContainer {
|
||||||
|
|
||||||
if let druid::Event::Command(c) = event {
|
if let druid::Event::Command(c) = event {
|
||||||
if let Some(idx) = c.get(crate::commands::RENAME_BLOCK) {
|
if let Some(idx) = c.get(crate::commands::RENAME_BLOCK) {
|
||||||
println!("rename block");
|
|
||||||
data.modals.rename_block = data::RenameBlock {
|
data.modals.rename_block = data::RenameBlock {
|
||||||
block_index: *idx,
|
|
||||||
name: data
|
name: data
|
||||||
.blocks
|
.blocks
|
||||||
.get(*idx)
|
.get(*idx)
|
||||||
|
@ -52,6 +50,7 @@ impl Widget<data::AppData> for ModalContainer {
|
||||||
.get(*idx)
|
.get(*idx)
|
||||||
.map(|i| i.name.clone())
|
.map(|i| i.name.clone())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
block_index: *idx,
|
||||||
};
|
};
|
||||||
self.modal = Some(WidgetPod::new(rename_block()).boxed());
|
self.modal = Some(WidgetPod::new(rename_block()).boxed());
|
||||||
ctx.children_changed();
|
ctx.children_changed();
|
||||||
|
@ -106,11 +105,9 @@ impl Widget<data::AppData> for ModalContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self, ctx: &mut druid::PaintCtx, data: &data::AppData, env: &druid::Env) {
|
fn paint(&mut self, ctx: &mut druid::PaintCtx, data: &data::AppData, env: &druid::Env) {
|
||||||
println!("paint modal container");
|
|
||||||
self.child.paint(ctx, data, env);
|
self.child.paint(ctx, data, env);
|
||||||
let full_rect = ctx.size().to_rect();
|
let full_rect = ctx.size().to_rect();
|
||||||
if let Some(m) = self.modal.as_mut() {
|
if let Some(m) = self.modal.as_mut() {
|
||||||
println!("painting modal");
|
|
||||||
ctx.fill(full_rect, &druid::Color::rgba8(0, 0, 0, 100));
|
ctx.fill(full_rect, &druid::Color::rgba8(0, 0, 0, 100));
|
||||||
m.paint(ctx, data, env)
|
m.paint(ctx, data, env)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use abacus_core::Output;
|
use abacus_core::Output;
|
||||||
use druid::{
|
use druid::{
|
||||||
widget::{Flex, Label, Padding, ViewSwitcher},
|
widget::{Container, Flex, Label, Padding, Scroll, ViewSwitcher},
|
||||||
Color, FontDescriptor, FontFamily, FontWeight, Widget, WidgetExt,
|
Color, FontDescriptor, FontFamily, FontWeight, Widget, WidgetExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,7 +64,8 @@ pub fn output_block() -> impl Widget<Block> {
|
||||||
Output::DataFrame(frame) => {
|
Output::DataFrame(frame) => {
|
||||||
let mut flex = Flex::row();
|
let mut flex = Flex::row();
|
||||||
for series in frame.iter() {
|
for series in frame.iter() {
|
||||||
let mut col = Flex::column();
|
let mut col = Flex::column()
|
||||||
|
.cross_axis_alignment(druid::widget::CrossAxisAlignment::Fill);
|
||||||
col.add_child(
|
col.add_child(
|
||||||
Label::new(series.name())
|
Label::new(series.name())
|
||||||
.with_font(
|
.with_font(
|
||||||
|
@ -72,9 +73,10 @@ pub fn output_block() -> impl Widget<Block> {
|
||||||
.with_weight(FontWeight::BLACK),
|
.with_weight(FontWeight::BLACK),
|
||||||
)
|
)
|
||||||
.with_text_size(OUTPUT_FONT_SIZE)
|
.with_text_size(OUTPUT_FONT_SIZE)
|
||||||
.expand_width()
|
.with_text_color(Color::rgb8(150, 150, 150))
|
||||||
.padding(3.0)
|
.padding(3.0)
|
||||||
.border(Color::rgb8(60, 60, 60), 1.0),
|
.border(Color::rgb8(50, 50, 50), 1.0)
|
||||||
|
.background(Color::rgb8(40, 40, 40)),
|
||||||
);
|
);
|
||||||
|
|
||||||
for v in series.iter() {
|
for v in series.iter() {
|
||||||
|
@ -85,21 +87,25 @@ pub fn output_block() -> impl Widget<Block> {
|
||||||
.with_weight(FontWeight::MEDIUM),
|
.with_weight(FontWeight::MEDIUM),
|
||||||
)
|
)
|
||||||
.with_text_size(OUTPUT_FONT_SIZE)
|
.with_text_size(OUTPUT_FONT_SIZE)
|
||||||
.expand_width()
|
|
||||||
.padding(3.0)
|
.padding(3.0)
|
||||||
.border(Color::rgb8(60, 60, 60), 1.0),
|
.border(Color::rgb8(50, 50, 50), 1.0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
flex.add_flex_child(col, 1.0);
|
flex.add_child(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
Box::new(Padding::new(
|
Box::new(Padding::new(
|
||||||
25.0,
|
25.0,
|
||||||
flex.padding(10.0)
|
Container::new(
|
||||||
.background(Color::rgb8(30, 30, 30))
|
Scroll::new(flex.background(Color::rgb8(30, 30, 30)).expand_width())
|
||||||
.rounded(4.0)
|
.horizontal()
|
||||||
.expand_width(),
|
.expand_width(),
|
||||||
|
)
|
||||||
|
.rounded(4.0)
|
||||||
|
.border(Color::rgb8(30, 30, 30), 5.0)
|
||||||
|
.background(Color::rgb8(30, 30, 30))
|
||||||
|
.expand_width(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
_ => Box::new(Padding::new(
|
_ => Box::new(Padding::new(
|
||||||
|
@ -110,7 +116,8 @@ pub fn output_block() -> impl Widget<Block> {
|
||||||
)
|
)
|
||||||
.with_text_size(0.0)
|
.with_text_size(0.0)
|
||||||
.padding(0.0)
|
.padding(0.0)
|
||||||
.background(Color::TRANSPARENT),
|
.background(Color::TRANSPARENT)
|
||||||
|
.expand_width(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,336 @@
|
||||||
|
use piet_common::{kurbo, Color, LineCap, Piet, RenderContext, StrokeDash, StrokeStyle};
|
||||||
|
use plotters_backend::{BackendColor, BackendCoord, DrawingBackend, DrawingErrorKind};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct Error {}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "plotters-piet error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
/// The piet backend.
|
||||||
|
///
|
||||||
|
/// Note that the size of the piet context has to be specified here.
|
||||||
|
pub struct PietBackend<'a, 'b> {
|
||||||
|
pub size: (u32, u32),
|
||||||
|
pub render_ctx: &'a mut Piet<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> std::fmt::Debug for PietBackend<'a, 'b> {
|
||||||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
fmt.debug_struct("PietBackend")
|
||||||
|
.field("size", &self.size)
|
||||||
|
.field("render_ctx", &"(not printable)")
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> DrawingBackend for PietBackend<'a, 'b> {
|
||||||
|
type ErrorType = Error;
|
||||||
|
|
||||||
|
fn get_size(&self) -> (u32, u32) {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
self.render_ctx
|
||||||
|
.finish()
|
||||||
|
.map_err(|_| DrawingErrorKind::DrawingError(Error {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_pixel(
|
||||||
|
&mut self,
|
||||||
|
point: BackendCoord,
|
||||||
|
color: BackendColor,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
let x = point.0 as f64;
|
||||||
|
let y = point.1 as f64;
|
||||||
|
self.render_ctx.fill(
|
||||||
|
kurbo::Rect::new(x, y, x + 1., y + 1.),
|
||||||
|
&plotters_color_to_piet(&color),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_line<S: plotters_backend::BackendStyle>(
|
||||||
|
&mut self,
|
||||||
|
from: BackendCoord,
|
||||||
|
to: BackendCoord,
|
||||||
|
style: &S,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
let from = plotters_point_to_kurbo_mid(from);
|
||||||
|
let to = plotters_point_to_kurbo_mid(to);
|
||||||
|
|
||||||
|
self.render_ctx.stroke_styled(
|
||||||
|
kurbo::Line::new(from, to),
|
||||||
|
&plotters_color_to_piet(&style.color()),
|
||||||
|
style.stroke_width() as f64,
|
||||||
|
&STROKE_STYLE_SQUARE_CAP,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_rect<S: plotters_backend::BackendStyle>(
|
||||||
|
&mut self,
|
||||||
|
upper_left: BackendCoord,
|
||||||
|
bottom_right: BackendCoord,
|
||||||
|
style: &S,
|
||||||
|
fill: bool,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
let color = plotters_color_to_piet(&style.color());
|
||||||
|
|
||||||
|
if fill {
|
||||||
|
let upper_left = plotters_point_to_kurbo_corner(upper_left);
|
||||||
|
let mut bottom_right = plotters_point_to_kurbo_corner(bottom_right);
|
||||||
|
bottom_right.x += 1.;
|
||||||
|
bottom_right.y += 1.;
|
||||||
|
let rect = kurbo::Rect::new(upper_left.x, upper_left.y, bottom_right.x, bottom_right.y);
|
||||||
|
|
||||||
|
self.render_ctx.fill(rect, &color);
|
||||||
|
} else {
|
||||||
|
let upper_left = plotters_point_to_kurbo_mid(upper_left);
|
||||||
|
let bottom_right = plotters_point_to_kurbo_mid(bottom_right);
|
||||||
|
let rect = kurbo::Rect::new(upper_left.x, upper_left.y, bottom_right.x, bottom_right.y);
|
||||||
|
|
||||||
|
self.render_ctx
|
||||||
|
.stroke(rect, &color, style.stroke_width() as f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_path<S: plotters_backend::BackendStyle, I: IntoIterator<Item = BackendCoord>>(
|
||||||
|
&mut self,
|
||||||
|
path: I,
|
||||||
|
style: &S,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
if style.color().alpha == 0.0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let path: Vec<kurbo::PathEl> = plotters_path_to_kurbo(path).collect();
|
||||||
|
|
||||||
|
self.render_ctx.stroke_styled(
|
||||||
|
&*path,
|
||||||
|
&plotters_color_to_piet(&style.color()),
|
||||||
|
style.stroke_width() as f64,
|
||||||
|
&STROKE_STYLE_SQUARE_CAP,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_circle<S: plotters_backend::BackendStyle>(
|
||||||
|
&mut self,
|
||||||
|
center: BackendCoord,
|
||||||
|
radius: u32,
|
||||||
|
style: &S,
|
||||||
|
fill: bool,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
let center = plotters_point_to_kurbo_mid(center);
|
||||||
|
let color = plotters_color_to_piet(&style.color());
|
||||||
|
let circle = kurbo::Circle::new(center, radius as f64);
|
||||||
|
|
||||||
|
if fill {
|
||||||
|
self.render_ctx.fill(circle, &color);
|
||||||
|
} else {
|
||||||
|
self.render_ctx
|
||||||
|
.stroke(circle, &color, style.stroke_width() as f64);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_polygon<S: plotters_backend::BackendStyle, I: IntoIterator<Item = BackendCoord>>(
|
||||||
|
&mut self,
|
||||||
|
vert: I,
|
||||||
|
style: &S,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
if style.color().alpha == 0.0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let path: Vec<kurbo::PathEl> = plotters_path_to_kurbo(vert)
|
||||||
|
.chain(std::iter::once(kurbo::PathEl::ClosePath))
|
||||||
|
.collect();
|
||||||
|
self.render_ctx
|
||||||
|
.fill(&*path, &plotters_color_to_piet(&style.color()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now we use the default text drawing provided by plotters. This is definitely slower,
|
||||||
|
// but at least we don't have to worry about matching the font size and offset which turns
|
||||||
|
// out to be trickier than expected.
|
||||||
|
// fn draw_text<TStyle: plotters_backend::BackendTextStyle>(
|
||||||
|
// &mut self,
|
||||||
|
// text: &str,
|
||||||
|
// style: &TStyle,
|
||||||
|
// pos: BackendCoord,
|
||||||
|
// ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
// let pos = plotters_point_to_kurbo(pos);
|
||||||
|
// let color = plotters_color_to_piet(&style.color());
|
||||||
|
|
||||||
|
// let text_api = self.render_ctx.text();
|
||||||
|
// let font_family = match style.family() {
|
||||||
|
// plotters_backend::FontFamily::Serif => Ok(FontFamily::SERIF),
|
||||||
|
// plotters_backend::FontFamily::SansSerif => Ok(FontFamily::SANS_SERIF),
|
||||||
|
// plotters_backend::FontFamily::Monospace => Ok(FontFamily::MONOSPACE),
|
||||||
|
// plotters_backend::FontFamily::Name(name) => text_api
|
||||||
|
// .font_family(name)
|
||||||
|
// .ok_or(piet_common::Error::MissingFont),
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let (font_style, weight) = match style.style() {
|
||||||
|
// plotters_backend::FontStyle::Normal => (FontStyle::Regular, FontWeight::REGULAR),
|
||||||
|
// plotters_backend::FontStyle::Oblique => (FontStyle::Italic, FontWeight::REGULAR),
|
||||||
|
// plotters_backend::FontStyle::Italic => (FontStyle::Italic, FontWeight::REGULAR),
|
||||||
|
// plotters_backend::FontStyle::Bold => (FontStyle::Regular, FontWeight::BOLD),
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let alignment = match style.anchor().h_pos {
|
||||||
|
// plotters_backend::text_anchor::HPos::Left => TextAlignment::Start,
|
||||||
|
// plotters_backend::text_anchor::HPos::Right => TextAlignment::End,
|
||||||
|
// plotters_backend::text_anchor::HPos::Center => TextAlignment::Center,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let layout = text_api
|
||||||
|
// .new_text_layout(String::from(text))
|
||||||
|
// .font(font_family.unwrap(), style.size())
|
||||||
|
// .text_color(color)
|
||||||
|
// .alignment(alignment)
|
||||||
|
// .default_attribute(TextAttribute::Style(font_style))
|
||||||
|
// .default_attribute(TextAttribute::Weight(weight))
|
||||||
|
// .build()
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
|
// // todo: style.anchor().v_pos
|
||||||
|
// // todo: style.transform()
|
||||||
|
|
||||||
|
// self.render_ctx.draw_text(&layout, pos);
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plotters_color_to_piet(col: &BackendColor) -> piet_common::Color {
|
||||||
|
Color::rgba8(col.rgb.0, col.rgb.1, col.rgb.2, (col.alpha * 256.) as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plotters_point_to_kurbo_mid((x, y): BackendCoord) -> kurbo::Point {
|
||||||
|
kurbo::Point {
|
||||||
|
x: x as f64 + 0.5,
|
||||||
|
y: y as f64 + 0.5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plotters_point_to_kurbo_corner((x, y): BackendCoord) -> kurbo::Point {
|
||||||
|
kurbo::Point {
|
||||||
|
x: x as f64,
|
||||||
|
y: y as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is basically just an iterator map that applies a different function on
|
||||||
|
/// the first item as on the later items.
|
||||||
|
/// We need this because the piet direct2d backend doesn't like it if a path
|
||||||
|
/// consists entirely of `LineTo` entries, it requires the first entry to be
|
||||||
|
/// a `MoveTo` entry.
|
||||||
|
struct PlottersPathToKurbo<I> {
|
||||||
|
iter: I,
|
||||||
|
first: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> PlottersPathToKurbo<I> {
|
||||||
|
fn new(path: I) -> PlottersPathToKurbo<I> {
|
||||||
|
PlottersPathToKurbo {
|
||||||
|
iter: path,
|
||||||
|
first: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> Iterator for PlottersPathToKurbo<I>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = BackendCoord>,
|
||||||
|
{
|
||||||
|
type Item = kurbo::PathEl;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.iter.next().map(|point| {
|
||||||
|
let point = plotters_point_to_kurbo_mid(point);
|
||||||
|
|
||||||
|
if self.first {
|
||||||
|
self.first = false;
|
||||||
|
kurbo::PathEl::MoveTo(point)
|
||||||
|
} else {
|
||||||
|
kurbo::PathEl::LineTo(point)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plotters_path_to_kurbo(
|
||||||
|
path: impl IntoIterator<Item = BackendCoord>,
|
||||||
|
) -> impl Iterator<Item = kurbo::PathEl> {
|
||||||
|
PlottersPathToKurbo::new(path.into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
const STROKE_STYLE_SQUARE_CAP: StrokeStyle = StrokeStyle::new().line_cap(LineCap::Square);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use piet_common::RenderContext;
|
||||||
|
use plotters::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fill_root_white() {
|
||||||
|
let width = 3;
|
||||||
|
let height = 2;
|
||||||
|
|
||||||
|
let mut device = piet_common::Device::new().unwrap();
|
||||||
|
let mut bitmap = device.bitmap_target(width, height, 1.0).unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut render_ctx = bitmap.render_context();
|
||||||
|
|
||||||
|
let piet_backend = PietBackend {
|
||||||
|
size: (width as u32, height as u32),
|
||||||
|
render_ctx: &mut render_ctx,
|
||||||
|
};
|
||||||
|
|
||||||
|
let root = piet_backend.into_drawing_area();
|
||||||
|
root.fill(&WHITE).unwrap();
|
||||||
|
|
||||||
|
render_ctx.finish().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = [0; 6 * 4];
|
||||||
|
bitmap
|
||||||
|
.copy_raw_pixels(piet_common::ImageFormat::RgbaPremul, &mut buf)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(buf, [255; 6 * 4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_plotters_path_to_kurbo() {
|
||||||
|
let path = vec![(1, 2), (3, 4), (5, 6)];
|
||||||
|
|
||||||
|
let kurbo_path: Vec<kurbo::PathEl> = plotters_path_to_kurbo(path).collect();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
kurbo_path,
|
||||||
|
vec![
|
||||||
|
kurbo::PathEl::MoveTo(kurbo::Point { x: 1.5, y: 2.5 }),
|
||||||
|
kurbo::PathEl::LineTo(kurbo::Point { x: 3.5, y: 4.5 }),
|
||||||
|
kurbo::PathEl::LineTo(kurbo::Point { x: 5.5, y: 6.5 }),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
use crate::piet_plotters::PietBackend;
|
||||||
|
use druid::{Data, Widget};
|
||||||
|
use plotters::{
|
||||||
|
coord::Shift,
|
||||||
|
prelude::{DrawingArea, IntoDrawingArea},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Plot<T: Data> {
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
plot: Box<dyn Fn((u32, u32), &T, &DrawingArea<PietBackend, Shift>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Data> Plot<T> {
|
||||||
|
pub fn new(f: impl Fn((u32, u32), &T, &DrawingArea<PietBackend, Shift>) + 'static) -> Plot<T> {
|
||||||
|
Plot { plot: Box::new(f) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Widget<T> for Plot<T>
|
||||||
|
where
|
||||||
|
T: Data,
|
||||||
|
{
|
||||||
|
fn event(&mut self, _: &mut druid::EventCtx, _: &druid::Event, _: &mut T, _: &druid::Env) {}
|
||||||
|
|
||||||
|
fn lifecycle(
|
||||||
|
&mut self,
|
||||||
|
_: &mut druid::LifeCycleCtx,
|
||||||
|
_: &druid::LifeCycle,
|
||||||
|
_: &T,
|
||||||
|
_: &druid::Env,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &mut druid::UpdateCtx, old_data: &T, data: &T, _env: &druid::Env) {
|
||||||
|
if !old_data.same(data) {
|
||||||
|
ctx.request_paint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
_: &mut druid::LayoutCtx,
|
||||||
|
bc: &druid::BoxConstraints,
|
||||||
|
_: &T,
|
||||||
|
_: &druid::Env,
|
||||||
|
) -> druid::Size {
|
||||||
|
bc.max()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self, ctx: &mut druid::PaintCtx, data: &T, _: &druid::Env) {
|
||||||
|
let druid::Size { width, height } = ctx.size();
|
||||||
|
let size = (width as u32, height as u32);
|
||||||
|
let backend = PietBackend {
|
||||||
|
size,
|
||||||
|
render_ctx: ctx.render_ctx,
|
||||||
|
};
|
||||||
|
|
||||||
|
(self.plot)(size, data, &backend.into_drawing_area());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"name": "Things",
|
|
||||||
"content": "let df = dataframe(#{\n names: [\"Alice\", \"Charles\", \"Bob\"], \n ages: [18,25,31],\n id: [1,2,3],\n amount: [10,0,12]\n});\n\n//df.select([column(\"ages\").filter(column(\"ages\") gt 20)]);\n\nlet df2 = from df [\"names\", \"ages\"] : [\"ages\" gt 20];\ndf2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"name": "Things",
|
|
||||||
"content": "let x = 1;\nx+1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
Reference in New Issue